sync.ts 3.8 KB

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