sync.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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. cloudSync() {
  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()
  55. .toLocaleTimeString()
  56. .replace(/:/g, "_")}`
  57. : new Date().toLocaleString();
  58. const fileName = `Backup-${datePart}.json`;
  59. downloadAs(JSON.stringify(state), fileName);
  60. },
  61. async import() {
  62. const rawContent = await readFromFile();
  63. try {
  64. const remoteState = JSON.parse(rawContent) as AppState;
  65. const localState = getLocalAppState();
  66. mergeAppState(localState, remoteState);
  67. setLocalAppState(localState);
  68. location.reload();
  69. } catch (e) {
  70. console.error("[Import]", e);
  71. showToast(Locale.Settings.Sync.ImportFailed);
  72. }
  73. },
  74. getClient() {
  75. const provider = get().provider;
  76. const client = createSyncClient(provider, get());
  77. return client;
  78. },
  79. async sync() {
  80. const localState = getLocalAppState();
  81. const provider = get().provider;
  82. const config = get()[provider];
  83. const client = this.getClient();
  84. try {
  85. const remoteState = await client.get(config.username);
  86. if (!remoteState || remoteState === "") {
  87. console.log("[Sync] Remote state is empty, using local state instead.");
  88. return
  89. } else {
  90. const parsedRemoteState = JSON.parse(
  91. await client.get(config.username),
  92. ) as AppState;
  93. mergeAppState(localState, parsedRemoteState);
  94. setLocalAppState(localState);
  95. }
  96. } catch (e) {
  97. console.log("[Sync] failed to get remote state", e);
  98. throw e;
  99. }
  100. await client.set(config.username, JSON.stringify(localState));
  101. this.markSyncTime();
  102. },
  103. async check() {
  104. const client = this.getClient();
  105. return await client.check();
  106. },
  107. }),
  108. {
  109. name: StoreKey.Sync,
  110. version: 1.2,
  111. migrate(persistedState, version) {
  112. const newState = persistedState as typeof DEFAULT_SYNC_STATE;
  113. if (version < 1.1) {
  114. newState.upstash.username = STORAGE_KEY;
  115. }
  116. if (version < 1.2) {
  117. if (
  118. (persistedState as typeof DEFAULT_SYNC_STATE).proxyUrl ===
  119. "/api/cors/"
  120. ) {
  121. newState.proxyUrl = "";
  122. }
  123. }
  124. return newState as any;
  125. },
  126. },
  127. );