import { HttpClient } from '@angular/common/http'; import { inject, Injectable, PLATFORM_ID } from '@angular/core'; import { BehaviorSubject, map, Observable } 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); public accessTokenSub = new BehaviorSubject(null); http = inject(HttpClient); router = inject(Router); private readonly storageKey = 'accessToken'; tokenReady$ = new BehaviorSubject(null); constructor() { this.accessToken = this.safeGetToken(); } 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; } } get currentToken(): string | null { return this.safeGetToken(); } safeSetToken(token: string) { this.accessToken = token; if (isPlatformBrowser(this.platformId)) { localStorage.setItem(this.storageKey, token); this.accessTokenSub.next(token); } } private safeGetToken(): string | null { try { if (isPlatformBrowser(this.platformId)) { const token = localStorage.getItem(this.storageKey); this.accessTokenSub.next(token); return token; } } catch (e) { console.warn('Failed to read from localStorage:', e); } return null; } private safeRemoveToken() { this.accessToken = null; if (isPlatformBrowser(this.platformId)) { localStorage.removeItem(this.storageKey); this.accessTokenSub.next(null); } } 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; } logout(): void { this.http.post(this.api('/api/v1/auth/logout'), {}).subscribe(); this.accessToken = null; this.safeRemoveToken(); this.router.navigateByUrl('login'); } refreshToken(): Observable { return this.http.post(this.api('/api/v1/auth/RefreshToken'), {}); } getApiKey(): string { return environment.apiKey; } isLoggedIn(): boolean { return this.safeGetToken() != null; } }