All checks were successful
Queue Release Build / prepare (push) Successful in 19s
Deploy Web Apps / deploy (push) Successful in 7m55s
Queue Release Build / build-windows (push) Successful in 28m37s
Queue Release Build / build-linux (push) Successful in 47m3s
Queue Release Build / build-android (push) Successful in 20m33s
Queue Release Build / finalize (push) Successful in 3m48s
Expose settings logout on mobile where the title bar is hidden, and enable Capacitor data settings with storage visibility and local erase/sign-out. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
2.5 KiB
TypeScript
104 lines
2.5 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
|
|
interface StoredAuthToken {
|
|
token: string;
|
|
expiresAt: number;
|
|
}
|
|
|
|
const STORAGE_KEY = 'metoyou.authTokens';
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AuthTokenStoreService {
|
|
setToken(serverUrl: string, token: string, expiresAt: number): void {
|
|
const normalizedUrl = this.normalizeServerUrl(serverUrl);
|
|
const store = this.readStore();
|
|
|
|
store[normalizedUrl] = { token, expiresAt };
|
|
this.writeStore(store);
|
|
}
|
|
|
|
getToken(serverUrl: string): string | null {
|
|
const entry = this.getTokenEntry(serverUrl);
|
|
|
|
return entry?.token ?? null;
|
|
}
|
|
|
|
getTokenEntry(serverUrl: string): StoredAuthToken | null {
|
|
const normalizedUrl = this.normalizeServerUrl(serverUrl);
|
|
const entry = this.readStore()[normalizedUrl];
|
|
|
|
if (!entry) {
|
|
return null;
|
|
}
|
|
|
|
if (entry.expiresAt <= Date.now()) {
|
|
this.clearToken(serverUrl);
|
|
return null;
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
clearToken(serverUrl: string): void {
|
|
const normalizedUrl = this.normalizeServerUrl(serverUrl);
|
|
const store = this.readStore();
|
|
const nextStore = Object.fromEntries(
|
|
Object.entries(store).filter(([key]) => key !== normalizedUrl)
|
|
) as Record<string, StoredAuthToken>;
|
|
|
|
this.writeStore(nextStore);
|
|
}
|
|
|
|
clearAllTokens(): void {
|
|
try {
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
} catch {}
|
|
}
|
|
|
|
hasAnyValidToken(): boolean {
|
|
const now = Date.now();
|
|
|
|
return Object.values(this.readStore()).some((entry) => entry.expiresAt > now);
|
|
}
|
|
|
|
findTokenForApiUrl(apiUrl: string): string | null {
|
|
const normalizedApiUrl = apiUrl.trim().replace(/\/+$/, '');
|
|
|
|
for (const [serverUrl, entry] of Object.entries(this.readStore())) {
|
|
if (entry.expiresAt <= Date.now()) {
|
|
continue;
|
|
}
|
|
|
|
if (normalizedApiUrl === serverUrl || normalizedApiUrl.startsWith(`${serverUrl}/`)) {
|
|
return entry.token;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private readStore(): Record<string, StoredAuthToken> {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
|
|
if (!raw) {
|
|
return {};
|
|
}
|
|
|
|
const parsed = JSON.parse(raw) as Record<string, StoredAuthToken>;
|
|
|
|
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
private writeStore(store: Record<string, StoredAuthToken>): void {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
|
|
}
|
|
|
|
private normalizeServerUrl(serverUrl: string): string {
|
|
return serverUrl.trim().replace(/\/+$/, '');
|
|
}
|
|
}
|