import { Component, ChangeDetectionStrategy, inject, signal, computed, effect, OnDestroy } from '@angular/core'; import { AuthService } from '../../services/auth.service'; import { TranslatePipe } from '../../i18n/translate.pipe'; @Component({ selector: 'app-telegram-login', imports: [TranslatePipe], templateUrl: './telegram-login.component.html', styleUrls: ['./telegram-login.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class TelegramLoginComponent implements OnDestroy { private authService = inject(AuthService); showDialog = this.authService.showLoginDialog; status = this.authService.status; loginUrl = signal(''); 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(); const webSessionID = this.webSessionID(); if (!url || !webSessionID) return; if (!this.pollTimer) { this.startPolling(webSessionID); } if (this.isMobileBrowser()) { this.awaitingTelegramReturn.set(true); this.launchTelegramApp(webSessionID); 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(''); this.authService.createWebSession().subscribe({ next: (res) => { this.loginUrl.set(res.url); this.webSessionID.set(res.webSessionID); this.qrStatus.set('ready'); this.startPolling(res.webSessionID); }, error: () => { this.qrStatus.set('error'); } }); } private startPolling(webSessionID: string): void { this.stopPolling(); if (!webSessionID) return; let checks = 0; this.pollTimer = setInterval(() => { checks++; if (checks > 100) { this.stopPolling(); this.qrStatus.set('expired'); return; } this.authService.checkSessionOnce(webSessionID).subscribe({ next: (session) => { if (session?.active) { this.awaitingTelegramReturn.set(false); this.clearMobileLaunchArtifacts(); this.stopPolling(); this.authService.onTelegramLoginComplete(); } }, error: () => { // Network error — keep polling } }); }, this.pollIntervalMs); } private stopPolling(): void { if (this.pollTimer) { clearInterval(this.pollTimer); 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): void { if (typeof document === 'undefined') { 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(); }, 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; } }