import { HttpClient } from '@angular/common/http'; import { inject, Injectable, PLATFORM_ID } from '@angular/core'; import { BehaviorSubject, map, Observable, of } from 'rxjs'; import { environment } from '../../environments/environment'; import { isPlatformBrowser } from '@angular/common'; import { Router } from '@angular/router'; interface ValidateOtpResponse { accessToken: string; } interface RefreshTokenResponse { accessToken: string; } @Injectable({ providedIn: 'root' }) export class AuthService { private readonly baseUrl = (environment.apiUrl ?? '').replace(/\/+$/, ''); private accessToken: string | null = null; private platformId = inject(PLATFORM_ID); private accessTokenSub = new BehaviorSubject(null); tokenReady$ = new BehaviorSubject(false); http = inject(HttpClient); router = inject(Router); private readonly storageKey = 'accessToken'; private api(path: string) { return `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`; } // Call on app start, or from guard async ensureTokenReady(): Promise{ if(this.tokenReady$.value) return; const stored = this.safeGetToken(); // try to restore from storage if(stored){ this.accessTokenSub.next(stored); this.tokenReady$.next(true); return; } // // Optionally: try a silent refresh on startup to restore session using HttpOnly cookie // try { // const res = await firstValueFrom(this.refreshToken()); // this.safeSetToken(res.accessToken); // } // catch{ // console.warn('Silent token refresh failed on startup'); // } // finally{ // this.tokenReady$.next(true); // } } safeSetToken(token: string) { this.accessToken = token; if (isPlatformBrowser(this.platformId)) { localStorage.setItem(this.storageKey, token); } } private safeGetToken(): string | null { if (isPlatformBrowser(this.platformId)) { return localStorage.getItem(this.storageKey); } return null; } private safeRemoveToken() { this.accessToken = null; if (isPlatformBrowser(this.platformId)) { localStorage.removeItem(this.storageKey); } } sendOtp(email: string): Observable { const formData = new FormData(); formData.append('email', email); return this.http.post(this.api('/api/v1/auth/GenerateOtp'), formData); } verifyOtp(userId: string, otpCode: string): Observable { const body = { UserId: userId, OtpCode: otpCode }; return this.http.post(this.api('/api/v1/auth/ValidateOtp'), body).pipe(map((response: ValidateOtpResponse) => { if (response && response.accessToken) { this.accessToken = response.accessToken; this.safeSetToken(response.accessToken); } })); } getAccessToken(): string | null { return this.accessToken ?? this.safeGetToken(); } logout(): Observable { this.accessToken = null; this.safeRemoveToken(); this.router.navigate(['/login']); return of(); } refreshToken(): Observable { return this.http.post(this.api('/api/v1/auth/RefreshToken'), {}); } getApiKey(): string { return environment.apiKey; } isLoggedIn(): boolean { return this.safeGetToken() != null; } }