feat: implement user storage provider with clear functionality and update root state service for local storage integration

This commit is contained in:
Lorenz Hilpert
2025-03-21 11:00:24 +01:00
parent db7da0699e
commit 452de44f34
8 changed files with 72 additions and 20 deletions

View File

@@ -1,28 +1,32 @@
import { Injectable } from '@angular/core';
import { Logger, LogLevel } from '@core/logger';
import { Store } from '@ngrx/store';
import { UserStateService } from '@generated/swagger/isa-api';
import { debounceTime, switchMap, takeUntil } from 'rxjs/operators';
import { RootState } from './root.state';
import packageInfo from 'packageJson';
import { environment } from '../../environments/environment';
import { Subject } from 'rxjs';
import { AuthService } from '@core/auth';
import { injectStorage, UserStorageProvider } from '@isa/core/storage';
import { isEqual } from 'lodash';
@Injectable({ providedIn: 'root' })
export class RootStateService {
static LOCAL_STORAGE_KEY = 'ISA_APP_INITIALSTATE';
#storage = injectStorage(UserStorageProvider);
private _cancelSave = new Subject<void>();
constructor(
private readonly _authService: AuthService,
private readonly _userStateService: UserStateService,
private _logger: Logger,
private _store: Store,
) {
if (!environment.production) {
console.log('Die UserState kann in der Konsole mit der Funktion "clearUserState()" geleert werden.');
console.log(
'Die UserState kann in der Konsole mit der Funktion "clearUserState()" geleert werden.',
);
}
window['clearUserState'] = () => {
@@ -43,13 +47,17 @@ export class RootStateService {
takeUntil(this._cancelSave),
debounceTime(1000),
switchMap((state) => {
const raw = JSON.stringify({
const data = {
...state,
version: packageInfo.version,
sub: this._authService.getClaimByKey('sub'),
};
RootStateService.SaveToLocalStorageRaw(JSON.stringify(data));
return this.#storage.set('state', {
...state,
version: packageInfo.version,
sub: this._authService.getClaimByKey('sub'),
});
RootStateService.SaveToLocalStorageRaw(raw);
return this._userStateService.UserStateSetUserState({ content: raw });
}),
)
.subscribe();
@@ -60,16 +68,15 @@ export class RootStateService {
*/
async load(): Promise<boolean> {
try {
const res = await this._userStateService.UserStateGetUserState().toPromise();
const res = await this.#storage.get('state');
const resContent = res?.result?.content ?? null;
const storageContent = RootStateService.LoadFromLocalStorageRaw();
if (resContent) {
RootStateService.SaveToLocalStorageRaw(res.result.content);
if (res) {
RootStateService.SaveToLocalStorageRaw(JSON.stringify(res));
}
if (resContent !== storageContent) {
if (!isEqual(res, storageContent)) {
return true;
}
} catch (error) {
@@ -81,7 +88,7 @@ export class RootStateService {
async clear() {
try {
this._cancelSave.next();
await this._userStateService.UserStateResetUserState().toPromise();
await this.#storage.clear('state');
await new Promise((resolve) => setTimeout(resolve, 100));
RootStateService.RemoveFromLocalStorage();
await new Promise((resolve) => setTimeout(resolve, 100));

View File

@@ -4,3 +4,4 @@ export * from './lib/session.storage-provider';
export * from './lib/signal-store-feature';
export * from './lib/storage-provider';
export * from './lib/storage';
export * from './lib/user.storage-provider';

View File

@@ -58,4 +58,14 @@ export class IDBStorageProvider implements StorageProvider {
request.onerror = (event) => reject(event);
});
}
async clear(key: string): Promise<void> {
const store = await this.getObjectStore('readwrite');
return new Promise<void>((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = (event) => reject(event);
});
}
}

View File

@@ -14,4 +14,8 @@ export class LocalStorageProvider implements StorageProvider {
}
return data;
}
async clear(key: string): Promise<void> {
localStorage.removeItem(key);
}
}

View File

@@ -13,4 +13,8 @@ export class SessionStorageProvider implements StorageProvider {
}
return data;
}
async clear(key: string): Promise<void> {
sessionStorage.removeItem(key);
}
}

View File

@@ -2,4 +2,6 @@ export interface StorageProvider {
set(key: string, value: unknown): Promise<void>;
get(key: string): Promise<unknown>;
clear(key: string): Promise<void>;
}

View File

@@ -33,6 +33,18 @@ export class Storage {
}
return data;
}
async clear(token: string | object): Promise<void> {
let key: string;
if (typeof token === 'string') {
key = token;
} else {
key = hash(token);
}
return this.storageProvider.clear(key);
}
}
const storageMap = new WeakMap<Type<StorageProvider>, Storage>();

View File

@@ -1,14 +1,24 @@
import { inject, Injectable } from '@angular/core';
import { StorageProvider } from './storage-provider';
import { UserStateService } from '@generated/swagger/isa-api';
import { firstValueFrom } from 'rxjs';
import { firstValueFrom, map, shareReplay } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class UserStorageProvider implements StorageProvider {
#userStateService = inject(UserStateService);
private state$ = this.#userStateService.UserStateGetUserState().pipe(
map((res) => {
if (res.result?.content) {
return JSON.parse(res.result.content);
}
return {};
}),
shareReplay(1),
);
async set(key: string, value: unknown): Promise<void> {
const current = (await this.get(key)) || {};
const current = await firstValueFrom(this.state$);
firstValueFrom(
this.#userStateService.UserStateSetUserState({
content: JSON.stringify({ ...current, [key]: value }),
@@ -17,12 +27,14 @@ export class UserStorageProvider implements StorageProvider {
}
async get(key: string): Promise<unknown> {
const res = await firstValueFrom(this.#userStateService.UserStateGetUserState());
const userState = await firstValueFrom(this.state$);
return userState[key];
}
if (res.result?.content) {
return JSON.parse(res.result.content)[key];
}
return undefined;
async clear(key: string): Promise<void> {
const current = await firstValueFrom(this.state$);
delete current[key];
firstValueFrom(this.#userStateService.UserStateResetUserState());
}
}