Files
Toju/toju-app/src/app/infrastructure/persistence/app-resume.storage.ts
2026-05-23 15:28:40 +02:00

195 lines
4.8 KiB
TypeScript

import { STORAGE_KEY_GENERAL_SETTINGS, STORAGE_KEY_LAST_VIEWED_CHAT } from '../../core/constants';
import { getUserScopedStorageKey } from '../../core/storage/current-user-storage';
import { runWhenIdle } from '../../shared/rxjs';
export interface GeneralSettings {
reopenLastViewedChat: boolean;
}
export const DEFAULT_GENERAL_SETTINGS: GeneralSettings = {
reopenLastViewedChat: true
};
export interface LastViewedChatSnapshot {
userId: string;
roomId: string;
channelId: string | null;
}
const pendingWrites = new Map<string, string | null>();
let flushScheduled = false;
let cancelScheduledFlush: (() => void) | null = null;
function scheduleStorageFlush(): void {
if (flushScheduled) {
return;
}
flushScheduled = true;
cancelScheduledFlush = runWhenIdle(() => {
flushScheduled = false;
cancelScheduledFlush = null;
const snapshot = Array.from(pendingWrites.entries());
pendingWrites.clear();
for (const [key, value] of snapshot) {
try {
if (value === null) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
}
} catch {
// storage not available
}
}
});
}
function scheduleStorageWrite(key: string, serialised: string): void {
pendingWrites.set(key, serialised);
scheduleStorageFlush();
}
function scheduleStorageRemove(key: string): void {
pendingWrites.set(key, null);
scheduleStorageFlush();
}
function readMaybePending(key: string): string | null {
if (pendingWrites.has(key)) {
return pendingWrites.get(key) ?? null;
}
try {
return localStorage.getItem(key);
} catch {
return null;
}
}
export function loadGeneralSettingsFromStorage(): GeneralSettings {
try {
const raw = readMaybePending(getUserScopedStorageKey(STORAGE_KEY_GENERAL_SETTINGS))
?? readMaybePending(STORAGE_KEY_GENERAL_SETTINGS);
if (!raw) {
return { ...DEFAULT_GENERAL_SETTINGS };
}
return normaliseGeneralSettings(JSON.parse(raw) as Partial<GeneralSettings>);
} catch {
return { ...DEFAULT_GENERAL_SETTINGS };
}
}
export function saveGeneralSettingsToStorage(patch: Partial<GeneralSettings>): GeneralSettings {
const nextSettings = normaliseGeneralSettings({
...loadGeneralSettingsFromStorage(),
...patch
});
scheduleStorageWrite(getUserScopedStorageKey(STORAGE_KEY_GENERAL_SETTINGS), JSON.stringify(nextSettings));
return nextSettings;
}
export function loadLastViewedChatFromStorage(userId?: string | null): LastViewedChatSnapshot | null {
try {
const raw = readMaybePending(getUserScopedStorageKey(STORAGE_KEY_LAST_VIEWED_CHAT, userId))
?? readMaybePending(STORAGE_KEY_LAST_VIEWED_CHAT);
if (!raw) {
return null;
}
const snapshot = normaliseLastViewedChatSnapshot(JSON.parse(raw) as Partial<LastViewedChatSnapshot>);
if (!snapshot) {
return null;
}
if (userId && snapshot.userId !== userId) {
return null;
}
return snapshot;
} catch {
return null;
}
}
export function saveLastViewedChatToStorage(snapshot: LastViewedChatSnapshot): void {
const normalised = normaliseLastViewedChatSnapshot(snapshot);
if (!normalised) {
return;
}
scheduleStorageWrite(
getUserScopedStorageKey(STORAGE_KEY_LAST_VIEWED_CHAT, normalised.userId),
JSON.stringify(normalised)
);
}
export function clearLastViewedChatFromStorage(userId?: string | null): void {
scheduleStorageRemove(getUserScopedStorageKey(STORAGE_KEY_LAST_VIEWED_CHAT, userId));
scheduleStorageRemove(STORAGE_KEY_LAST_VIEWED_CHAT);
}
/** Force-flush any pending app-resume writes (e.g. before unload). */
export function flushAppResumeStorage(): void {
if (cancelScheduledFlush) {
cancelScheduledFlush();
cancelScheduledFlush = null;
}
flushScheduled = false;
const snapshot = Array.from(pendingWrites.entries());
pendingWrites.clear();
for (const [key, value] of snapshot) {
try {
if (value === null) {
localStorage.removeItem(key);
} else {
localStorage.setItem(key, value);
}
} catch {
// storage not available
}
}
}
function normaliseGeneralSettings(raw: Partial<GeneralSettings>): GeneralSettings {
return {
reopenLastViewedChat:
typeof raw.reopenLastViewedChat === 'boolean'
? raw.reopenLastViewedChat
: DEFAULT_GENERAL_SETTINGS.reopenLastViewedChat
};
}
function normaliseLastViewedChatSnapshot(raw: Partial<LastViewedChatSnapshot>): LastViewedChatSnapshot | null {
const userId = typeof raw.userId === 'string' ? raw.userId.trim() : '';
const roomId = typeof raw.roomId === 'string' ? raw.roomId.trim() : '';
const channelId = typeof raw.channelId === 'string'
? raw.channelId.trim() || null
: null;
if (!userId || !roomId) {
return null;
}
return {
userId,
roomId,
channelId
};
}