sync.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  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 "@/app/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. await client.set(config.username, JSON.stringify(localState));
  88. console.log(
  89. "[Sync] Remote state is empty, using local state instead.",
  90. );
  91. return;
  92. } else {
  93. const parsedRemoteState = JSON.parse(
  94. await client.get(config.username),
  95. ) as AppState;
  96. mergeAppState(localState, parsedRemoteState);
  97. setLocalAppState(localState);
  98. }
  99. } catch (e) {
  100. console.log("[Sync] failed to get remote state", e);
  101. throw e;
  102. }
  103. await client.set(config.username, JSON.stringify(localState));
  104. this.markSyncTime();
  105. },
  106. async check() {
  107. const client = this.getClient();
  108. return await client.check();
  109. },
  110. }),
  111. {
  112. name: StoreKey.Sync,
  113. version: 1.2,
  114. migrate(persistedState, version) {
  115. const newState = persistedState as typeof DEFAULT_SYNC_STATE;
  116. if (version < 1.1) {
  117. newState.upstash.username = STORAGE_KEY;
  118. }
  119. if (version < 1.2) {
  120. if (
  121. (persistedState as typeof DEFAULT_SYNC_STATE).proxyUrl ===
  122. "/api/cors/"
  123. ) {
  124. newState.proxyUrl = "";
  125. }
  126. }
  127. return newState as any;
  128. },
  129. },
  130. );