sync.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import { getClientConfig } from "../config/client";
  2. import { Updater } from "../typing";
  3. import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
  4. import { createPersistStore } from "../utils/store";
  5. import {
  6. AppState,
  7. getLocalAppState,
  8. GetStoreState,
  9. mergeAppState,
  10. setLocalAppState,
  11. } from "../utils/sync";
  12. import { downloadAs, readFromFile } from "../utils";
  13. import { showToast } from "../components/ui-lib";
  14. import Locale from "../locales";
  15. import { createSyncClient, ProviderType } from "../utils/cloud";
  16. import { corsPath } from "../utils/cors";
  17. export interface WebDavConfig {
  18. server: string;
  19. username: string;
  20. password: string;
  21. }
  22. const isApp = !!getClientConfig()?.isApp;
  23. export type SyncStore = GetStoreState<typeof useSyncStore>;
  24. const DEFAULT_SYNC_STATE = {
  25. provider: ProviderType.WebDAV,
  26. useProxy: true,
  27. proxyUrl: corsPath(ApiPath.Cors),
  28. webdav: {
  29. endpoint: "",
  30. username: "",
  31. password: "",
  32. },
  33. upstash: {
  34. endpoint: "",
  35. username: STORAGE_KEY,
  36. apiKey: "",
  37. },
  38. lastSyncTime: 0,
  39. lastProvider: "",
  40. };
  41. export const useSyncStore = createPersistStore(
  42. DEFAULT_SYNC_STATE,
  43. (set, get) => ({
  44. coundSync() {
  45. const config = get()[get().provider];
  46. return Object.values(config).every((c) => c.toString().length > 0);
  47. },
  48. markSyncTime() {
  49. set({ lastSyncTime: Date.now(), lastProvider: get().provider });
  50. },
  51. export() {
  52. const state = getLocalAppState();
  53. const datePart = isApp
  54. ? `${new Date().toLocaleDateString().replace(/\//g, '_')} ${new Date().toLocaleTimeString().replace(/:/g, '_')}`
  55. : new Date().toLocaleString();
  56. const fileName = `Backup-${datePart}.json`;
  57. downloadAs(JSON.stringify(state), fileName);
  58. },
  59. async import() {
  60. const rawContent = await readFromFile();
  61. try {
  62. const remoteState = JSON.parse(rawContent) as AppState;
  63. const localState = getLocalAppState();
  64. mergeAppState(localState, remoteState);
  65. setLocalAppState(localState);
  66. location.reload();
  67. } catch (e) {
  68. console.error("[Import]", e);
  69. showToast(Locale.Settings.Sync.ImportFailed);
  70. }
  71. },
  72. getClient() {
  73. const provider = get().provider;
  74. const client = createSyncClient(provider, get());
  75. return client;
  76. },
  77. async sync() {
  78. const localState = getLocalAppState();
  79. const provider = get().provider;
  80. const config = get()[provider];
  81. const client = this.getClient();
  82. try {
  83. const remoteState = JSON.parse(
  84. await client.get(config.username),
  85. ) as AppState;
  86. mergeAppState(localState, remoteState);
  87. setLocalAppState(localState);
  88. } catch (e) {
  89. console.log("[Sync] failed to get remote state", e);
  90. }
  91. await client.set(config.username, JSON.stringify(localState));
  92. this.markSyncTime();
  93. },
  94. async check() {
  95. const client = this.getClient();
  96. return await client.check();
  97. },
  98. }),
  99. {
  100. name: StoreKey.Sync,
  101. version: 1.1,
  102. migrate(persistedState, version) {
  103. const newState = persistedState as typeof DEFAULT_SYNC_STATE;
  104. if (version < 1.1) {
  105. newState.upstash.username = STORAGE_KEY;
  106. }
  107. return newState as any;
  108. },
  109. },
  110. );