added remote user config support

This commit is contained in:
2026-07-09 17:42:14 +02:00
parent 8bd2a64487
commit c76b408f7e
2 changed files with 192 additions and 84 deletions
+125 -17
View File
@@ -9,10 +9,17 @@ import { UpdateUserResponse } from './types'
import { reactive } from 'vue'
import { API } from './main'
import { applyTheme } from './themeLoader.ts';
import { setLanguage } from './i18n'
let store: Store | null = null
export const initAuth = getAuthData
export interface UserConfig {
language: string | null;
theme: string | null;
compact_layout: boolean | null;
}
async function getStore() {
if (!store) store = await load('store.json')
return store
@@ -75,17 +82,116 @@ export async function getLastRoom(): Promise<string | null> {
return (await s.get<string>('last_room_uuid')) ?? null
}
// ==== Global config logic ====
export async function getLocalConfig(): Promise<UserConfig> {
const s = await getStore();
let config = await s.get<UserConfig>('config');
if (!config) {
// Fallback migration check for old config fields
const legacyTheme = await s.get<string>('theme');
const legacyLang = await s.get<string>('language');
const legacyCompact = await s.get<boolean>('compact_layout');
config = {
language: legacyLang ?? null,
theme: legacyTheme ?? null,
compact_layout: legacyCompact ?? null
};
await s.set('config', config);
await s.save();
}
return config;
}
export async function saveLocalConfig(config: UserConfig) {
const s = await getStore();
await s.set('config', config);
await s.save();
}
// Queue system to ensure config changes sync reliably with the API
let isSyncing = false;
let pendingConfig: UserConfig | null = null;
const retryDelays = [500, 2000, 5000];
async function syncConfigWithRetry(config: UserConfig) {
pendingConfig = config;
if (isSyncing) return;
isSyncing = true;
while (pendingConfig !== null) {
const configToUpload = pendingConfig;
pendingConfig = null;
let success = false;
let attempt = 0;
while (!success) {
const auth = await getAuthData();
if (!auth.token) {
// Stop syncing if the session is cleared
isSyncing = false;
return;
}
try {
await apiFetch<UserConfig>('/account/save-config', {
method: 'POST',
body: JSON.stringify(configToUpload),
});
success = true;
} catch (error) {
const delay = retryDelays[attempt] ?? 5000;
attempt++;
console.warn(`Sync failed. Retrying in ${delay / 1000}s...`, error);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
isSyncing = false;
}
export async function fetchAndApplyRemoteConfig() {
const auth = await getAuthData();
if (!auth.token) return;
try {
const remoteConfig = await apiFetch<UserConfig>('/account/get-config');
if (remoteConfig) {
await saveLocalConfig(remoteConfig);
if (remoteConfig.theme) {
applyTheme(remoteConfig.theme);
}
if (remoteConfig.language) {
setLanguage(remoteConfig.language);
}
}
} catch (error) {
console.error("Failed to fetch remote config:", error);
}
}
// ==== Language preferences ====
export async function saveLocalePreference(locale: string) {
const s = await getStore()
await s.set('language', locale)
await s.save()
const config = await getLocalConfig();
config.language = locale;
await saveLocalConfig(config);
await syncConfigWithRetry(config);
}
export async function getLocalePreference(): Promise<string | null> {
const s = await getStore()
return (await s.get<string>('language')) ?? null
const config = await getLocalConfig();
return config.language;
}
// ==== Auth operations ====
export async function login(email: string, username: string, password: string) {
const res: LoginResponse = await apiFetch('/login', {
@@ -100,6 +206,7 @@ export async function login(email: string, username: string, password: string) {
avatar_url: getAvatar(res.uuid)
};
await saveAuthData(res.token, user)
await fetchAndApplyRemoteConfig()
return { token: res.token, uuid: res.uuid, isAuthenticated: true }
}
@@ -189,21 +296,21 @@ export function refreshAvatar(uuid: string) {
// ==== Color themes ====
export async function saveThemePreference(themeId: string) {
const s = await getStore();
await s.set('theme', themeId);
await s.save();
const config = await getLocalConfig();
config.theme = themeId;
await saveLocalConfig(config);
applyTheme(themeId);
await syncConfigWithRetry(config);
}
export async function initTheme() {
const s = await getStore();
const themeId = (await s.get<string>('theme')) || 'default';
const themeId = await getThemePreference();
applyTheme(themeId);
}
export async function getThemePreference(): Promise<string> {
const s = await getStore()
return (await s.get<string>('theme')) ?? 'default'
const config = await getLocalConfig();
return config.theme ?? 'default';
}
export async function applyStoredTheme() {
@@ -214,14 +321,15 @@ export async function applyStoredTheme() {
// ==== Layout ====
export async function saveCompactLayoutPreference(enabled: boolean) {
const s = await getStore();
await s.set('compact_layout', enabled);
await s.save();
const config = await getLocalConfig();
config.compact_layout = enabled;
await saveLocalConfig(config);
await syncConfigWithRetry(config);
}
export async function getCompactLayoutPreference(): Promise<boolean> {
const s = await getStore();
return (await s.get<boolean>('compact_layout')) ?? false;
const config = await getLocalConfig();
return config.compact_layout ?? false;
}
// ==== Message draft ====