Files
FastCheck/src/app/auth-dialog/auth-dialog.ts

248 lines
6.4 KiB
TypeScript
Raw Normal View History

2026-05-25 00:38:22 +04:00
import { HttpClient } from '@angular/common/http';
import { Component, computed, effect, inject, input, output, signal } from '@angular/core';
import { FASTCHECK_API } from '../api';
import { TranslatePipe } from '../translate/translate.pipe';
interface WebSessionResponse {
sessionId: string;
userId: string;
expires: string;
userSessionId: string;
Status: boolean;
}
export type AuthDialogMode = 'payment' | 'login' | 'new';
export interface AuthDialogAuthorizedEvent {
sessionId: string;
userId: string;
userSessionId: string;
}
type AuthDialogState = 'loading' | 'ready' | 'checking' | 'expired' | 'error';
@Component({
selector: 'app-auth-dialog',
imports: [TranslatePipe],
templateUrl: './auth-dialog.html',
styleUrl: './auth-dialog.scss'
})
export class AuthDialogComponent {
private readonly http = inject(HttpClient);
2026-05-25 16:18:43 +04:00
private readonly telegramBot = 'myAMLKYCBOT';
2026-05-25 00:38:22 +04:00
private readonly sessionStorageKey = 'fc_session';
private readonly maxPollAttempts = 100;
open = input(false);
mode = input<AuthDialogMode>('login');
processing = input(false);
authorized = output<AuthDialogAuthorizedEvent>();
closed = output<void>();
state = signal<AuthDialogState>('loading');
webSessionId = signal('');
messageKey = signal('');
telegramLink = computed(() => {
const sessionId = this.webSessionId();
return sessionId
? `https://t.me/${this.telegramBot}?start=${encodeURIComponent(sessionId)}`
: `https://t.me/${this.telegramBot}`;
});
qrUrl = computed(() => {
const link = this.telegramLink();
return `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(link)}`;
});
get isMobile(): boolean {
return typeof window !== 'undefined' && window.innerWidth < 768;
}
private pollHandle: ReturnType<typeof setInterval> | null = null;
private pollAttempts = 0;
private wasOpen = false;
private authenticated = false;
constructor() {
effect(() => {
const isOpen = this.open();
if (isOpen && !this.wasOpen) {
this.wasOpen = true;
this.startAuthFlow();
return;
}
if (!isOpen && this.wasOpen) {
this.wasOpen = false;
this.finishFlow();
}
});
effect(() => {
if (!this.open()) return;
if (!this.processing()) return;
this.state.set('checking');
});
}
requestClose(): void {
this.closed.emit();
}
openTelegram(): void {
const link = this.telegramLink();
if (!link) return;
window.open(link, '_blank', 'noopener');
}
refreshQr(): void {
this.cleanupSession(false);
this.startAuthFlow();
}
private startAuthFlow(): void {
this.stopPolling();
this.authenticated = false;
this.pollAttempts = 0;
this.messageKey.set('');
this.webSessionId.set('');
this.state.set('checking');
const existingSession = localStorage.getItem(this.sessionStorageKey) ?? '';
if (existingSession) {
this.checkExistingSession(existingSession);
return;
}
this.createSession();
}
private checkExistingSession(sessionId: string): void {
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession/${sessionId}`).subscribe({
next: (response) => {
if (response?.Status) {
this.webSessionId.set(sessionId);
this.handleAuthorized(response, sessionId);
return;
}
localStorage.removeItem(this.sessionStorageKey);
this.createSession();
},
error: () => {
localStorage.removeItem(this.sessionStorageKey);
this.createSession();
}
});
}
private createSession(): void {
this.state.set('loading');
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession`).subscribe({
next: (response) => {
const sessionId = response?.sessionId ?? '';
if (!sessionId) {
this.messageKey.set('auth.session_failed');
this.state.set('error');
return;
}
this.webSessionId.set(sessionId);
if (this.isMobile) {
this.state.set('checking');
window.location.href = this.telegramLink();
this.startPolling(sessionId);
return;
}
this.state.set('ready');
this.startPolling(sessionId);
},
error: () => {
this.messageKey.set('auth.session_failed');
this.state.set('error');
}
});
}
private startPolling(sessionId: string): void {
this.stopPolling();
this.pollAttempts = 0;
this.pollHandle = setInterval(() => {
this.pollAttempts += 1;
if (this.pollAttempts >= this.maxPollAttempts) {
this.stopPolling();
this.messageKey.set('auth.expired');
this.state.set('expired');
return;
}
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession/${sessionId}`).subscribe({
next: (response) => {
if (!response?.Status) return;
this.handleAuthorized(response, sessionId);
},
error: () => undefined
});
}, 5000);
}
private handleAuthorized(response: WebSessionResponse, sessionId: string): void {
if (this.authenticated) return;
this.authenticated = true;
this.stopPolling();
this.webSessionId.set(sessionId);
this.state.set('checking');
this.authorized.emit({
sessionId,
userId: response.userId ?? '',
userSessionId: response.userSessionId ?? ''
});
}
private finishFlow(): void {
const shouldPersistSession = this.authenticated && (this.mode() === 'login' || this.mode() === 'new');
this.cleanupSession(shouldPersistSession);
this.messageKey.set('');
this.webSessionId.set('');
this.state.set('loading');
this.authenticated = false;
this.pollAttempts = 0;
}
private cleanupSession(persistSession: boolean): void {
this.stopPolling();
const sessionId = this.webSessionId();
if (!sessionId) {
if (!persistSession) {
localStorage.removeItem(this.sessionStorageKey);
}
return;
}
if (persistSession) {
localStorage.setItem(this.sessionStorageKey, sessionId);
return;
}
this.http
.request('DELETE', `${FASTCHECK_API}/websession/${sessionId}`, {
body: { sessionId }
})
.subscribe({ error: () => undefined });
localStorage.removeItem(this.sessionStorageKey);
}
private stopPolling(): void {
if (this.pollHandle === null) return;
clearInterval(this.pollHandle);
this.pollHandle = null;
}
}