From 3208ef2b3b87182c480bb95f839289f1476cb8c4 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Sat, 20 Jun 2026 14:25:13 +0400 Subject: [PATCH] test login 2 --- .../telegram-login.component.ts | 131 +++++++++++++++++- src/app/services/auth.service.ts | 6 + 2 files changed, 131 insertions(+), 6 deletions(-) diff --git a/src/app/components/telegram-login/telegram-login.component.ts b/src/app/components/telegram-login/telegram-login.component.ts index 689e373..adc34bb 100644 --- a/src/app/components/telegram-login/telegram-login.component.ts +++ b/src/app/components/telegram-login/telegram-login.component.ts @@ -19,48 +19,92 @@ export class TelegramLoginComponent implements OnDestroy { webSessionID = signal(''); qrStatus = signal<'loading' | 'ready' | 'expired' | 'error'>('loading'); encodedQrUrl = computed(() => encodeURIComponent(this.loginUrl())); + awaitingTelegramReturn = signal(false); private readonly pollIntervalMs = 5000; private pollTimer?: ReturnType; + private mobileFallbackTimeout?: ReturnType; + private launchFrame?: HTMLIFrameElement; + private readonly handleVisibilityChange = () => { + if (typeof document !== 'undefined' && document.visibilityState === 'hidden') { + this.clearMobileLaunchArtifacts(); + return; + } + + this.checkLoginAfterReturn(); + }; + private readonly handleWindowFocus = () => { + this.checkLoginAfterReturn(); + }; + private readonly handlePageShow = () => { + this.checkLoginAfterReturn(); + }; constructor() { effect(() => { if (this.showDialog()) { this.initQrLogin(); } else { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); this.stopPolling(); } }); + + if (typeof window !== 'undefined') { + document.addEventListener('visibilitychange', this.handleVisibilityChange); + window.addEventListener('focus', this.handleWindowFocus); + window.addEventListener('pageshow', this.handlePageShow); + } } ngOnDestroy(): void { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); this.stopPolling(); + + if (typeof window !== 'undefined') { + document.removeEventListener('visibilitychange', this.handleVisibilityChange); + window.removeEventListener('focus', this.handleWindowFocus); + window.removeEventListener('pageshow', this.handlePageShow); + } } close(): void { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); this.authService.hideLogin(); this.stopPolling(); } openTelegramLogin(): void { const url = this.loginUrl(); - if (!url) return; + const webSessionID = this.webSessionID(); + if (!url || !webSessionID) return; - window.open(url, '_blank'); if (!this.pollTimer) { - const webSessionID = this.webSessionID(); - if (webSessionID) { - this.startPolling(webSessionID); - } + this.startPolling(webSessionID); } + + if (this.isMobileBrowser()) { + this.awaitingTelegramReturn.set(true); + this.launchTelegramApp(webSessionID, url); + return; + } + + window.open(url, '_blank', 'noopener,noreferrer'); } refreshQr(): void { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); this.stopPolling(); this.initQrLogin(); } private initQrLogin(): void { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); this.qrStatus.set('loading'); this.loginUrl.set(''); this.webSessionID.set(''); @@ -94,6 +138,8 @@ export class TelegramLoginComponent implements OnDestroy { this.authService.checkSessionOnce(webSessionID).subscribe({ next: (session) => { if (session?.active) { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); this.stopPolling(); this.authService.onTelegramLoginComplete(); } @@ -111,4 +157,77 @@ export class TelegramLoginComponent implements OnDestroy { this.pollTimer = undefined; } } + + private checkLoginAfterReturn(): void { + if (!this.showDialog() || !this.awaitingTelegramReturn()) { + return; + } + + const webSessionID = this.webSessionID(); + if (!webSessionID) { + this.awaitingTelegramReturn.set(false); + return; + } + + if (!this.pollTimer) { + this.startPolling(webSessionID); + } + + this.authService.checkSessionOnce(webSessionID).subscribe(session => { + if (session?.active) { + this.awaitingTelegramReturn.set(false); + this.clearMobileLaunchArtifacts(); + this.stopPolling(); + this.authService.onTelegramLoginComplete(); + } + }); + } + + private launchTelegramApp(webSessionID: string, fallbackUrl: string): void { + if (typeof document === 'undefined') { + window.location.assign(fallbackUrl); + return; + } + + this.clearMobileLaunchArtifacts(); + + this.launchFrame = document.createElement('iframe'); + this.launchFrame.style.display = 'none'; + this.launchFrame.setAttribute('aria-hidden', 'true'); + this.launchFrame.setAttribute('tabindex', '-1'); + this.launchFrame.src = this.authService.getTelegramAppLoginUrl(webSessionID); + document.body.appendChild(this.launchFrame); + + this.mobileFallbackTimeout = setTimeout(() => { + this.removeLaunchFrame(); + if (document.visibilityState === 'visible') { + window.location.assign(fallbackUrl); + } + }, 1400); + } + + private clearMobileLaunchArtifacts(): void { + if (this.mobileFallbackTimeout) { + clearTimeout(this.mobileFallbackTimeout); + this.mobileFallbackTimeout = undefined; + } + + this.removeLaunchFrame(); + } + + private removeLaunchFrame(): void { + this.launchFrame?.remove(); + this.launchFrame = undefined; + } + + private isMobileBrowser(): boolean { + if (typeof navigator === 'undefined') { + return false; + } + + const userAgent = navigator.userAgent || navigator.vendor; + const isTouchMac = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1; + + return /Android|iPhone|iPad|iPod|IEMobile|Opera Mini/i.test(userAgent) || isTouchMac; + } } diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 73c3d64..b3b767d 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -88,6 +88,12 @@ export class AuthService { return `https://t.me/${botUsername}?start=${encodeURIComponent(webSessionID)}`; } + /** Generate a Telegram app deep link for mobile login without opening a browser tab. */ + getTelegramAppLoginUrl(webSessionID: string): string { + const botUsername = this.getTelegramBotUsername(); + return `tg://resolve?domain=${encodeURIComponent(botUsername)}&start=${encodeURIComponent(webSessionID)}`; + } + /** Get QR code data URL for Telegram login */ getTelegramQrUrl(): string { return this.getTelegramLoginUrl();