var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { Injectable, signal, computed, inject } from '@angular/core'; import { tap } from 'rxjs'; import { TelegramSessionApiService } from './telegram-session-api.service'; const WEB_SESSION_COOKIE = 'webSessionID'; const WEB_SESSION_COOKIE_MAX_AGE_SECONDS = 60 * 60; /** Customer-facing Telegram QR/session auth. Distinct storage/state from AdminAuthService by design. */ let AuthService = class AuthService { constructor() { this.api = inject(TelegramSessionApiService); this.sessionSignal = signal(null); this.statusSignal = signal('unknown'); this.showLoginSignal = signal(false); /** Current auth session */ this.session = this.sessionSignal.asReadonly(); /** Current auth status */ this.status = this.statusSignal.asReadonly(); /** Whether user is fully authenticated */ this.isAuthenticated = computed(() => this.statusSignal() === 'authenticated'); /** Whether to show login dialog */ this.showLoginDialog = this.showLoginSignal.asReadonly(); /** Display name of authenticated user */ this.displayName = computed(() => this.sessionSignal()?.displayName ?? null); // On init, check existing session via cookie this.checkSession(); } /** Check the current webSessionID cookie against the auth backend. */ checkSession() { const webSessionID = this.getStoredWebSessionID(); if (!webSessionID) { this.clearAuthState('unauthenticated'); return; } this.statusSignal.set('checking'); this.checkSessionOnce(webSessionID).subscribe(session => { if (!session?.active) { this.clearAuthState('unauthenticated'); } }); } /** Check session without updating internal state beyond activating on success (used for polling). */ checkSessionOnce(webSessionID = this.getStoredWebSessionID()) { return this.api.checkSessionOnce(webSessionID).pipe(tap(session => { if (session?.active) { this.activateSession(session); } })); } /** * Called after user completes Telegram login. */ onTelegramLoginComplete() { this.hideLogin(); if (!this.isAuthenticated()) { this.checkSession(); } } /** Generate the Telegram login URL for bot-based auth */ getTelegramLoginUrl(webSessionID) { return this.api.getBotLoginUrl(webSessionID); } /** Generate a Telegram app deep link for mobile login without opening a browser tab. */ getTelegramAppLoginUrl(webSessionID) { return this.api.getBotAppLoginUrl(webSessionID); } /** Create a backend web session and return the Telegram start link for it. */ createWebSession() { return this.api.createSession(); } /** Show login dialog (called when user tries to pay without being logged in) */ requestLogin() { this.showLoginSignal.set(true); } /** Hide login dialog */ hideLogin() { this.showLoginSignal.set(false); } /** Logout — clears session on backend and locally */ logout() { const webSessionID = this.sessionSignal()?.sessionId || this.getStoredWebSessionID(); if (!webSessionID) { this.clearAuthState('unauthenticated'); return; } this.api.logout(webSessionID).subscribe(() => { this.clearAuthState('unauthenticated'); }); } activateSession(session) { this.sessionSignal.set(session); this.statusSignal.set('authenticated'); this.setStoredWebSessionID(session.sessionId); this.scheduleSessionRefresh(session.expires); } clearAuthState(status) { this.sessionSignal.set(null); this.statusSignal.set(status); this.clearStoredWebSessionID(); this.clearSessionRefresh(); } /** Schedule a session re-check before it expires */ scheduleSessionRefresh(expiresAt) { this.clearSessionRefresh(); const expiresMs = new Date(expiresAt).getTime(); const nowMs = Date.now(); // Re-check 60 seconds before expiry, minimum 30s from now const refreshIn = Number.isFinite(expiresMs) ? Math.max(expiresMs - nowMs - 60_000, 30_000) : WEB_SESSION_COOKIE_MAX_AGE_SECONDS * 1000; this.sessionCheckTimer = setTimeout(() => { this.checkSession(); }, refreshIn); } clearSessionRefresh() { if (this.sessionCheckTimer) { clearTimeout(this.sessionCheckTimer); this.sessionCheckTimer = undefined; } } getStoredWebSessionID() { if (typeof document === 'undefined') { return null; } const cookie = document.cookie .split('; ') .find(row => row.startsWith(`${WEB_SESSION_COOKIE}=`)); if (!cookie) { return null; } try { return decodeURIComponent(cookie.substring(WEB_SESSION_COOKIE.length + 1)); } catch { return null; } } setStoredWebSessionID(webSessionID) { if (typeof document === 'undefined') { return; } const secure = typeof window !== 'undefined' && window.location.protocol === 'https:' ? '; Secure' : ''; document.cookie = `${WEB_SESSION_COOKIE}=${encodeURIComponent(webSessionID)}; Max-Age=${WEB_SESSION_COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax${secure}`; } clearStoredWebSessionID() { if (typeof document === 'undefined') { return; } document.cookie = `${WEB_SESSION_COOKIE}=; Max-Age=0; Path=/; SameSite=Lax`; } }; AuthService = __decorate([ Injectable({ providedIn: 'root' }) ], AuthService); export { AuthService };