var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { Injectable, computed, signal } from '@angular/core'; import { JwtService } from './jwt.service'; const TOKEN_STORAGE_KEY = 'ed25519AdminToken'; const REFRESH_STORAGE_KEY = 'ed25519AdminRefreshToken'; /** Refresh this long before actual expiry, so a request never races an expiring token. */ const REFRESH_SKEW_MS = 60_000; /** * Holds the Ed25519-flow JWT/refresh-token pair and derived claims. Separate * from the telegram module's AdminAuthService (Telegram-session state) by * design - the two auth mechanisms are not merged until both ship on the * same backend and a migration decision is made. */ let SessionService = class SessionService { constructor() { this.jwt = new JwtService(); this.tokenSignal = signal(null); this.refreshTokenSignal = signal(null); this.claimsSignal = signal(null); this.statusSignal = signal('unknown'); this.token = this.tokenSignal.asReadonly(); this.claims = this.claimsSignal.asReadonly(); this.status = this.statusSignal.asReadonly(); this.isAuthenticated = computed(() => this.statusSignal() === 'authenticated'); this.role = computed(() => this.claimsSignal()?.role ?? null); } /** Called once by AuthService on init to wire up the refresh trigger without a circular DI dependency. */ onRefreshDue(callback) { this.refreshCallback = callback; } /** Restores session state from persisted storage. Returns true if a (possibly expired) session was found. */ restore() { this.statusSignal.set('restoring'); const token = this.readStorage(TOKEN_STORAGE_KEY); const refreshToken = this.readStorage(REFRESH_STORAGE_KEY); if (!token || !refreshToken) { this.statusSignal.set('unauthenticated'); return false; } const claims = this.jwt.decode(token); if (!claims) { this.clear(); return false; } this.tokenSignal.set(token); this.refreshTokenSignal.set(refreshToken); this.claimsSignal.set(claims); if (this.jwt.isExpired(claims)) { this.statusSignal.set('expired'); } else { this.statusSignal.set('authenticated'); this.scheduleRefresh(claims); } return true; } activate(tokens) { const claims = this.jwt.decode(tokens.token); if (!claims) { throw new Error('Received a malformed JWT from the auth backend.'); } this.tokenSignal.set(tokens.token); this.refreshTokenSignal.set(tokens.refreshToken); this.claimsSignal.set(claims); this.statusSignal.set('authenticated'); this.writeStorage(TOKEN_STORAGE_KEY, tokens.token); this.writeStorage(REFRESH_STORAGE_KEY, tokens.refreshToken); this.scheduleRefresh(claims); } getRefreshToken() { return this.refreshTokenSignal(); } markExpired() { this.statusSignal.set('expired'); this.clearRefreshTimer(); } clear() { this.tokenSignal.set(null); this.refreshTokenSignal.set(null); this.claimsSignal.set(null); this.statusSignal.set('unauthenticated'); this.removeStorage(TOKEN_STORAGE_KEY); this.removeStorage(REFRESH_STORAGE_KEY); this.clearRefreshTimer(); } scheduleRefresh(claims) { this.clearRefreshTimer(); const expiresInMs = claims.exp * 1000 - Date.now(); const refreshInMs = Math.max(expiresInMs - REFRESH_SKEW_MS, 5_000); this.refreshTimer = setTimeout(() => this.refreshCallback?.(), refreshInMs); } clearRefreshTimer() { if (this.refreshTimer) { clearTimeout(this.refreshTimer); this.refreshTimer = undefined; } } readStorage(key) { return typeof localStorage === 'undefined' ? null : localStorage.getItem(key); } writeStorage(key, value) { if (typeof localStorage !== 'undefined') { localStorage.setItem(key, value); } } removeStorage(key) { if (typeof localStorage !== 'undefined') { localStorage.removeItem(key); } } }; SessionService = __decorate([ Injectable({ providedIn: 'root' }) ], SessionService); export { SessionService };