Files
mokuro-reader/src/lib/settings/sync.ts

110 lines
3.0 KiB
TypeScript

import { browser } from '$app/environment';
import { derived, get, writable } from 'svelte/store';
import { debounce, showSnackbar } from '$lib/util';
import { currentProfile, profiles } from './settings';
import { miscSettings } from './misc';
import { volumes } from './volume-data';
export type SyncConfig = {
key: string | null;
auto: boolean;
notifications: boolean;
};
const stored = browser ? window.localStorage.getItem('syncConfig') : undefined;
const initial: SyncConfig = stored ? JSON.parse(stored) : { key: null, auto: false, notifications: true };
export const syncConfig = writable<SyncConfig>(initial);
syncConfig.subscribe((value) => {
if (browser) {
window.localStorage.setItem('syncConfig', JSON.stringify(value));
}
});
export const isSyncEnabled = derived(syncConfig, ($cfg) => Boolean($cfg.key));
async function callApi(method: 'GET' | 'POST', payload?: any) {
if (!browser) return null;
const url = method === 'GET' ? `/api/sync?key=${encodeURIComponent(get(syncConfig).key || '')}` : '/api/sync';
const res = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: method === 'POST' ? JSON.stringify(payload) : undefined
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
export async function pullAll() {
const cfg = get(syncConfig);
if (!cfg.key) throw new Error('No sync key configured');
const data = await callApi('GET');
if (!data) return;
if (data.profiles) {
profiles.set(data.profiles);
}
if (data.current_profile) {
currentProfile.set(data.current_profile);
}
if (data.misc_settings) {
miscSettings.set(data.misc_settings);
}
if (data.volumes) {
volumes.set(data.volumes);
}
if (cfg.notifications) {
showSnackbar('Pulled from server');
}
}
export async function pushAll() {
const cfg = get(syncConfig);
if (!cfg.key) throw new Error('No sync key configured');
const payload = {
key: cfg.key,
profiles: get(profiles),
current_profile: get(currentProfile),
misc_settings: get(miscSettings),
volumes: get(volumes)
};
await callApi('POST', payload);
if (cfg.notifications) {
showSnackbar('Pushed to server');
}
}
function setupAutoSync() {
if (!browser) return;
let unsubscribeFns: Array<() => void> = [];
function resubscribe() {
unsubscribeFns.forEach((fn) => fn());
unsubscribeFns = [];
if (!get(syncConfig).auto || !get(syncConfig).key) return;
const schedulePush = () => debounce(() => {
pushAll().catch((error) => {
console.error('Auto-sync failed:', error);
});
}, 300);
unsubscribeFns.push(profiles.subscribe(() => schedulePush()));
unsubscribeFns.push(currentProfile.subscribe(() => schedulePush()));
unsubscribeFns.push(miscSettings.subscribe(() => schedulePush()));
unsubscribeFns.push(volumes.subscribe(() => schedulePush()));
}
resubscribe();
unsubscribeFns.push(syncConfig.subscribe(() => resubscribe()));
}
if (browser) {
setupAutoSync();
}