Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
7.7 KiB
253 lines
7.7 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
import apiManager from '../lib/mtproto/mtprotoworker'; |
|
import Page from './page'; |
|
import serverTimeManager from '../lib/mtproto/serverTimeManager'; |
|
import { AuthAuthorization, AuthLoginToken } from '../layer'; |
|
import { bytesCmp, bytesToBase64 } from '../helpers/bytes'; |
|
import { pause } from '../helpers/schedulers'; |
|
import App from '../config/app'; |
|
import Button from '../components/button'; |
|
import { _i18n, i18n, LangPackKey } from '../lib/langPack'; |
|
import appStateManager from '../lib/appManagers/appStateManager'; |
|
import rootScope from '../lib/rootScope'; |
|
import { putPreloader } from '../components/misc'; |
|
import getLanguageChangeButton from '../components/languageChangeButton'; |
|
|
|
const FETCH_INTERVAL = 3; |
|
|
|
let onFirstMount = async() => { |
|
const pageElement = page.pageEl; |
|
const imageDiv = pageElement.querySelector('.auth-image') as HTMLDivElement; |
|
|
|
let preloader = putPreloader(imageDiv, true); |
|
|
|
const inputWrapper = document.createElement('div'); |
|
inputWrapper.classList.add('input-wrapper'); |
|
|
|
const btnBack = Button('btn-primary btn-secondary btn-primary-transparent primary', {text: 'Login.QR.Cancel'}); |
|
inputWrapper.append(btnBack); |
|
|
|
getLanguageChangeButton(inputWrapper); |
|
|
|
const container = imageDiv.parentElement; |
|
|
|
const h4 = document.createElement('h4'); |
|
_i18n(h4, 'Login.QR.Title'); |
|
|
|
const helpList = document.createElement('ol'); |
|
helpList.classList.add('qr-description'); |
|
(['Login.QR.Help1', 'Login.QR.Help2', 'Login.QR.Help3'] as LangPackKey[]).forEach((key) => { |
|
const li = document.createElement('li'); |
|
li.append(i18n(key)); |
|
helpList.append(li); |
|
}); |
|
|
|
container.append(h4, helpList, inputWrapper); |
|
|
|
btnBack.addEventListener('click', () => { |
|
import('./pageSignIn').then(m => m.default.mount()); |
|
stop = true; |
|
}); |
|
|
|
const results = await Promise.all([ |
|
import('qr-code-styling' as any) |
|
]); |
|
const QRCodeStyling = results[0].default; |
|
|
|
let stop = false; |
|
rootScope.addEventListener('user_auth', () => { |
|
stop = true; |
|
cachedPromise = null; |
|
}, true); |
|
|
|
let options: {dcId?: number, ignoreErrors: true} = {ignoreErrors: true}; |
|
let prevToken: Uint8Array | number[]; |
|
|
|
const iterate = async(isLoop: boolean) => { |
|
try { |
|
let loginToken = await apiManager.invokeApi('auth.exportLoginToken', { |
|
api_id: App.id, |
|
api_hash: App.hash, |
|
except_ids: [] |
|
}, {ignoreErrors: true}); |
|
|
|
if(loginToken._ === 'auth.loginTokenMigrateTo') { |
|
if(!options.dcId) { |
|
options.dcId = loginToken.dc_id; |
|
apiManager.setBaseDcId(loginToken.dc_id); |
|
//continue; |
|
} |
|
|
|
loginToken = await apiManager.invokeApi('auth.importLoginToken', { |
|
token: loginToken.token |
|
}, options) as AuthLoginToken.authLoginToken; |
|
} |
|
|
|
if(loginToken._ === 'auth.loginTokenSuccess') { |
|
const authorization = loginToken.authorization as any as AuthAuthorization.authAuthorization; |
|
apiManager.setUserAuth(authorization.user.id); |
|
import('./pageIm').then(m => m.default.mount()); |
|
return true; |
|
} |
|
|
|
/* // to base64 |
|
var decoder = new TextDecoder('utf8'); |
|
var b64encoded = btoa(String.fromCharCode.apply(null, [...loginToken.token])); */ |
|
|
|
if(!prevToken || !bytesCmp(prevToken, loginToken.token)) { |
|
prevToken = loginToken.token; |
|
|
|
let encoded = bytesToBase64(loginToken.token); |
|
let url = "tg://login?token=" + encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""); |
|
|
|
const style = window.getComputedStyle(document.documentElement); |
|
const surfaceColor = style.getPropertyValue('--surface-color').trim(); |
|
const textColor = style.getPropertyValue('--primary-text-color').trim(); |
|
const primaryColor = style.getPropertyValue('--primary-color').trim(); |
|
|
|
const logoUrl = await fetch('assets/img/logo_padded.svg') |
|
.then(res => res.text()) |
|
.then(text => { |
|
text = text.replace(/(fill:).+?(;)/, `$1${primaryColor}$2`); |
|
const blob = new Blob([text], {type: 'image/svg+xml;charset=utf-8'}); |
|
|
|
// * because iOS Safari doesn't want to eat objectURL |
|
return new Promise<string>((resolve) => { |
|
const reader = new FileReader(); |
|
reader.onload = (e) => { |
|
resolve(e.target.result as string); |
|
}; |
|
reader.readAsDataURL(blob); |
|
}); |
|
//return URL.createObjectURL(blob); |
|
}); |
|
|
|
const qrCode = new QRCodeStyling({ |
|
width: 240 * window.devicePixelRatio, |
|
height: 240 * window.devicePixelRatio, |
|
data: url, |
|
image: logoUrl, |
|
dotsOptions: { |
|
color: textColor, |
|
type: 'rounded' |
|
}, |
|
cornersSquareOptions: { |
|
type: 'extra-rounded' |
|
}, |
|
imageOptions: { |
|
imageSize: 1, |
|
margin: 0 |
|
}, |
|
backgroundOptions: { |
|
color: surfaceColor |
|
}, |
|
qrOptions: { |
|
errorCorrectionLevel: "L" |
|
} |
|
}); |
|
|
|
qrCode.append(imageDiv); |
|
(imageDiv.lastChild as HTMLCanvasElement).classList.add('qr-canvas'); |
|
|
|
let promise: Promise<void>; |
|
if(qrCode._drawingPromise) { |
|
promise = qrCode._drawingPromise; |
|
} else { |
|
promise = Promise.race([ |
|
pause(1000), |
|
new Promise<void>((resolve) => { |
|
qrCode._canvas._image.addEventListener('load', () => { |
|
window.requestAnimationFrame(() => resolve()); |
|
}, {once: true}); |
|
}) |
|
]); |
|
} |
|
|
|
// * это костыль, но библиотека не предоставляет никаких событий |
|
await promise.then(() => { |
|
if(preloader) { |
|
preloader.style.animation = 'hide-icon .4s forwards'; |
|
|
|
const c = imageDiv.children[1] as HTMLElement; |
|
c.style.display = 'none'; |
|
c.style.animation = 'grow-icon .4s forwards'; |
|
setTimeout(() => { |
|
c.style.display = ''; |
|
}, 150); |
|
|
|
setTimeout(() => { |
|
c.style.animation = ''; |
|
}, 500); |
|
preloader = undefined; |
|
} else { |
|
Array.from(imageDiv.children).slice(0, -1).forEach(el => { |
|
el.remove(); |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
if(isLoop) { |
|
let timestamp = Date.now() / 1000; |
|
let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset; |
|
|
|
await pause(diff > FETCH_INTERVAL ? 1e3 * FETCH_INTERVAL : 1e3 * diff | 0); |
|
} |
|
} catch(err) { |
|
switch(err.type) { |
|
case 'SESSION_PASSWORD_NEEDED': |
|
console.warn('pageSignQR: SESSION_PASSWORD_NEEDED'); |
|
err.handled = true; |
|
import('./pagePassword').then(m => m.default.mount()); |
|
stop = true; |
|
cachedPromise = null; |
|
break; |
|
default: |
|
console.error('pageSignQR: default error:', err); |
|
stop = true; |
|
break; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
}; |
|
|
|
//await iterate(false); |
|
|
|
return async() => { |
|
stop = false; |
|
|
|
do { |
|
if(stop) { |
|
break; |
|
} |
|
|
|
const needBreak = await iterate(true); |
|
if(needBreak) { |
|
break; |
|
} |
|
} while(true); |
|
}; |
|
}; |
|
|
|
let cachedPromise: Promise<() => Promise<void>>; |
|
const page = new Page('page-signQR', true, () => { |
|
return cachedPromise; |
|
}, () => { |
|
//console.log('onMount'); |
|
if(!cachedPromise) cachedPromise = onFirstMount(); |
|
cachedPromise.then(func => { |
|
func(); |
|
}); |
|
|
|
appStateManager.pushToState('authState', {_: 'authStateSignQr'}); |
|
}); |
|
|
|
export default page;
|
|
|