import type { AppState, GetStoreState, } from '../utils/sync'; import { showToast } from '../components/ui-lib'; import { getClientConfig } from '../config/client'; import { ApiPath, STORAGE_KEY, StoreKey } from '../constant'; import Locale from '../locales'; import { downloadAs, readFromFile } from '../utils'; import { createSyncClient, ProviderType } from '../utils/cloud'; import { createPersistStore } from '../utils/store'; import { getLocalAppState, mergeAppState, setLocalAppState, } from '../utils/sync'; export interface WebDavConfig { server: string; username: string; password: string; } const isApp = !!getClientConfig()?.isApp; export type SyncStore = GetStoreState; const DEFAULT_SYNC_STATE = { provider: ProviderType.WebDAV, useProxy: true, proxyUrl: ApiPath.Cors as string, webdav: { endpoint: '', username: '', password: '', }, upstash: { endpoint: '', username: STORAGE_KEY, apiKey: '', }, lastSyncTime: 0, lastProvider: '', }; export const useSyncStore = createPersistStore( DEFAULT_SYNC_STATE, (set, get) => ({ cloudSync() { const config = get()[get().provider]; return Object.values(config).every(c => c.toString().length > 0); }, markSyncTime() { set({ lastSyncTime: Date.now(), lastProvider: get().provider }); }, export() { const state = getLocalAppState(); const datePart = isApp ? `${new Date().toLocaleDateString().replace(/\//g, '_')} ${new Date() .toLocaleTimeString() .replace(/:/g, '_')}` : new Date().toLocaleString(); const fileName = `Backup-${datePart}.json`; downloadAs(JSON.stringify(state), fileName); }, async import() { const rawContent = await readFromFile(); try { const remoteState = JSON.parse(rawContent) as AppState; const localState = getLocalAppState(); mergeAppState(localState, remoteState); setLocalAppState(localState); location.reload(); } catch (e) { console.error('[Import]', e); showToast(Locale.Settings.Sync.ImportFailed); } }, getClient() { const provider = get().provider; const client = createSyncClient(provider, get()); return client; }, async sync() { const localState = getLocalAppState(); const provider = get().provider; const config = get()[provider]; const client = this.getClient(); try { const remoteState = await client.get(config.username); if (!remoteState || remoteState === '') { await client.set(config.username, JSON.stringify(localState)); console.log( '[Sync] Remote state is empty, using local state instead.', ); return; } else { const parsedRemoteState = JSON.parse( await client.get(config.username), ) as AppState; mergeAppState(localState, parsedRemoteState); setLocalAppState(localState); } } catch (e) { console.log('[Sync] failed to get remote state', e); throw e; } await client.set(config.username, JSON.stringify(localState)); this.markSyncTime(); }, async check() { const client = this.getClient(); return await client.check(); }, }), { name: StoreKey.Sync, version: 1.2, migrate(persistedState, version) { const newState = persistedState as typeof DEFAULT_SYNC_STATE; if (version < 1.1) { newState.upstash.username = STORAGE_KEY; } if (version < 1.2) { if ( (persistedState as typeof DEFAULT_SYNC_STATE).proxyUrl === '/api/cors/' ) { newState.proxyUrl = ''; } } return newState as any; }, }, );