248 lines
6.4 KiB
TypeScript
248 lines
6.4 KiB
TypeScript
|
|
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);
|
||
|
|
private readonly telegramBot = 'DexarSupport_bot';
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|