changed api

This commit is contained in:
sdarbinyan
2026-05-31 23:23:44 +04:00
parent 1d44edf4eb
commit a125732979
5 changed files with 86 additions and 37 deletions

View File

@@ -19,5 +19,12 @@
"changeOrigin": true, "changeOrigin": true,
"pathRewrite": { "^/proxy/api-vitanova": "" }, "pathRewrite": { "^/proxy/api-vitanova": "" },
"logLevel": "debug" "logLevel": "debug"
},
"/proxy/users-vitanova": {
"target": "https://users.vitanova.network:456",
"secure": true,
"changeOrigin": true,
"pathRewrite": { "^/proxy/users-vitanova": "" },
"logLevel": "debug"
} }
} }

View File

@@ -10,6 +10,14 @@ export const FASTCHECK_API = isDevMode()
? '/proxy/fastcheck-store/api' ? '/proxy/fastcheck-store/api'
: 'https://fastcheck.store/api'; : 'https://fastcheck.store/api';
/**
* Base URL for Telegram/websession authorization.
* Auth routes: POST /users/sessions, GET/DELETE /users/sessions/:webSessionID.
*/
export const USERS_VITANOVA_API = isDevMode()
? '/proxy/users-vitanova'
: 'https://users.vitanova.network:456';
/** /**
* Base URL for the QR/SBP backend (qr.vitanova.network). * Base URL for the QR/SBP backend (qr.vitanova.network).
* Used for the /api/settings endpoint that may return active check data on load. * Used for the /api/settings endpoint that may return active check data on load.

View File

@@ -30,6 +30,10 @@
{{ 'auth.telegram_btn' | translate }} {{ 'auth.telegram_btn' | translate }}
</button> </button>
<a class="telegram-link" [href]="telegramLink()" target="_blank" rel="noopener">
{{ telegramLink() }}
</a>
<div class="qr-section"> <div class="qr-section">
<p class="qr-hint">{{ 'auth.qr_hint' | translate }}</p> <p class="qr-hint">{{ 'auth.qr_hint' | translate }}</p>

View File

@@ -118,6 +118,20 @@ h2 {
flex-shrink: 0; flex-shrink: 0;
} }
.telegram-link {
display: block;
margin-top: 10px;
color: var(--telegram-hover);
font-size: 12px;
line-height: 1.4;
overflow-wrap: anywhere;
text-decoration: none;
}
.telegram-link:hover {
text-decoration: underline;
}
.qr-section { .qr-section {
margin-top: 20px; margin-top: 20px;
} }

View File

@@ -1,14 +1,20 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Component, computed, effect, inject, input, output, signal } from '@angular/core'; import { Component, computed, effect, inject, input, output, signal } from '@angular/core';
import { FASTCHECK_API } from '../api'; import { USERS_VITANOVA_API } from '../api';
import { TranslatePipe } from '../translate/translate.pipe'; import { TranslatePipe } from '../translate/translate.pipe';
interface WebSessionResponse { interface WebSessionResponse {
sessionId: string; sessionId?: string;
userId: string; webSessionID?: string;
expires: string; webSessionId?: string;
userSessionId: string; userId?: string;
Status: boolean; userID?: string;
telegramID?: string;
expires?: string;
userSessionId?: string;
userSessionID?: string;
Status?: boolean;
status?: boolean | string;
} }
export type AuthDialogMode = 'payment' | 'login' | 'new'; export type AuthDialogMode = 'payment' | 'login' | 'new';
@@ -31,7 +37,6 @@ export class AuthDialogComponent {
private readonly http = inject(HttpClient); private readonly http = inject(HttpClient);
private readonly telegramBot = 'myAMLKYCBOT'; private readonly telegramBot = 'myAMLKYCBOT';
private readonly sessionStorageKey = 'fc_session'; private readonly sessionStorageKey = 'fc_session';
private readonly maxPollAttempts = 100;
open = input(false); open = input(false);
mode = input<AuthDialogMode>('login'); mode = input<AuthDialogMode>('login');
@@ -61,7 +66,6 @@ export class AuthDialogComponent {
} }
private pollHandle: ReturnType<typeof setInterval> | null = null; private pollHandle: ReturnType<typeof setInterval> | null = null;
private pollAttempts = 0;
private wasOpen = false; private wasOpen = false;
private authenticated = false; private authenticated = false;
@@ -106,7 +110,6 @@ export class AuthDialogComponent {
private startAuthFlow(): void { private startAuthFlow(): void {
this.stopPolling(); this.stopPolling();
this.authenticated = false; this.authenticated = false;
this.pollAttempts = 0;
this.messageKey.set(''); this.messageKey.set('');
this.webSessionId.set(''); this.webSessionId.set('');
this.state.set('checking'); this.state.set('checking');
@@ -121,9 +124,9 @@ export class AuthDialogComponent {
} }
private checkExistingSession(sessionId: string): void { private checkExistingSession(sessionId: string): void {
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession/${sessionId}`).subscribe({ this.http.get<WebSessionResponse>(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`).subscribe({
next: (response) => { next: (response) => {
if (response?.Status) { if (this.isAuthorized(response)) {
this.webSessionId.set(sessionId); this.webSessionId.set(sessionId);
this.handleAuthorized(response, sessionId); this.handleAuthorized(response, sessionId);
return; return;
@@ -141,26 +144,30 @@ export class AuthDialogComponent {
private createSession(): void { private createSession(): void {
this.state.set('loading'); this.state.set('loading');
this.http.get<WebSessionResponse>(`${FASTCHECK_API}/websession`).subscribe({ const sessionId = this.createGuid();
this.webSessionId.set(sessionId);
this.http.post<WebSessionResponse>(`${USERS_VITANOVA_API}/users/sessions`, {
webSessionID: sessionId,
sessionId
}).subscribe({
next: (response) => { next: (response) => {
const sessionId = response?.sessionId ?? ''; const responseSessionId = this.getSessionId(response) || sessionId;
if (!sessionId) { if (!responseSessionId) {
this.messageKey.set('auth.session_failed'); this.messageKey.set('auth.session_failed');
this.state.set('error'); this.state.set('error');
return; return;
} }
this.webSessionId.set(sessionId); this.webSessionId.set(responseSessionId);
if (this.isMobile) { if (this.isAuthorized(response)) {
this.state.set('checking'); this.handleAuthorized(response, responseSessionId);
window.location.href = this.telegramLink();
this.startPolling(sessionId);
return; return;
} }
this.state.set('ready'); this.state.set('ready');
this.startPolling(sessionId); this.startPolling(responseSessionId);
}, },
error: () => { error: () => {
this.messageKey.set('auth.session_failed'); this.messageKey.set('auth.session_failed');
@@ -171,19 +178,10 @@ export class AuthDialogComponent {
private startPolling(sessionId: string): void { private startPolling(sessionId: string): void {
this.stopPolling(); this.stopPolling();
this.pollAttempts = 0;
this.pollHandle = setInterval(() => { this.pollHandle = setInterval(() => {
this.pollAttempts += 1; this.http.get<WebSessionResponse>(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`).subscribe({
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) => { next: (response) => {
if (!response?.Status) return; if (!this.isAuthorized(response)) return;
this.handleAuthorized(response, sessionId); this.handleAuthorized(response, sessionId);
}, },
error: () => undefined error: () => undefined
@@ -200,8 +198,8 @@ export class AuthDialogComponent {
this.state.set('checking'); this.state.set('checking');
this.authorized.emit({ this.authorized.emit({
sessionId, sessionId,
userId: response.userId ?? '', userId: response.userId ?? response.userID ?? response.telegramID ?? '',
userSessionId: response.userSessionId ?? '' userSessionId: response.userSessionId ?? response.userSessionID ?? ''
}); });
} }
@@ -212,7 +210,6 @@ export class AuthDialogComponent {
this.webSessionId.set(''); this.webSessionId.set('');
this.state.set('loading'); this.state.set('loading');
this.authenticated = false; this.authenticated = false;
this.pollAttempts = 0;
} }
private cleanupSession(persistSession: boolean): void { private cleanupSession(persistSession: boolean): void {
@@ -232,14 +229,33 @@ export class AuthDialogComponent {
} }
this.http this.http
.request('DELETE', `${FASTCHECK_API}/websession/${sessionId}`, { .delete(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`)
body: { sessionId }
})
.subscribe({ error: () => undefined }); .subscribe({ error: () => undefined });
localStorage.removeItem(this.sessionStorageKey); localStorage.removeItem(this.sessionStorageKey);
} }
private getSessionId(response: WebSessionResponse | null | undefined): string {
return response?.webSessionID ?? response?.webSessionId ?? response?.sessionId ?? '';
}
private isAuthorized(response: WebSessionResponse | null | undefined): boolean {
const status = response?.Status ?? response?.status;
return status === true || String(status).toLowerCase() === 'true';
}
private createGuid(): string {
if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
return crypto.randomUUID();
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => {
const randomValue = Math.floor(Math.random() * 16);
const value = char === 'x' ? randomValue : (randomValue & 0x3) | 0x8;
return value.toString(16);
});
}
private stopPolling(): void { private stopPolling(): void {
if (this.pollHandle === null) return; if (this.pollHandle === null) return;
clearInterval(this.pollHandle); clearInterval(this.pollHandle);