ChatGPT-Next-Web/app/store/sync.ts

151 lines
3.8 KiB
TypeScript

import { getClientConfig } from "../config/client";
import { Updater } from "../typing";
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";
import { corsPath } from "../utils/cors";
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: corsPath(ApiPath.Cors),
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;
},
},
);