Files
vitanovaPackages/dist/ed25519/session.service.js
2026-08-18 01:55:26 +04:00

121 lines
4.8 KiB
JavaScript

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 };