repo_description: ✨ Light and Fast AI Assistant. Support: Web | iOS | MacOS | Android |  Linux | Windows
repo_topics: calclaude chatgpt claude cross-platform desktop fe gemini gemini-pro gemini-server gemini-ultra gpt-4o groq nextjs ollama react tauri tauri-app vercel webui
code: import { getClientConfig } from "../config/client";
  import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
  import { createPersistStore } from "../utils/store";
  import {
    AppState,
    getLocalAppState,
    GetStoreState,
    mergeAppState,
    setLocalAppState,
  } from "../utils/sync";
  import { downloadAs, readFromFile } from "../utils";
  import { showToast } from "../components/ui-lib";
  import Locale from "../locales";
  import { createSyncClient, ProviderType } from "../utils/cloud";
  
  export interface WebDavConfig {
    server: string;
    username: string;
    password: string;
  }
  
  const isApp = !!getClientConfig()?.isApp;
  export type SyncStore = GetStoreState<typeof useSyncStore>;
  
  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;
      },
    },
  );
  