sync.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 = JSON.parse(
  86. await client.get(config.username),
  87. ) as AppState;
  88. mergeAppState(localState, remoteState);
  89. setLocalAppState(localState);
  90. } catch (e) {
  91. console.log("[Sync] failed to get remote state", e);
  92. }
  93. await client.set(config.username, JSON.stringify(localState));
  94. this.markSyncTime();
  95. },
  96. async check() {
  97. const client = this.getClient();
  98. return await client.check();
  99. },
  100. }),
  101. {
  102. name: StoreKey.Sync,
  103. version: 1.2,
  104. migrate(persistedState, version) {
  105. const newState = persistedState as typeof DEFAULT_SYNC_STATE;
  106. if (version < 1.1) {
  107. newState.upstash.username = STORAGE_KEY;
  108. }
  109. if (version < 1.2) {
  110. if (
  111. (persistedState as typeof DEFAULT_SYNC_STATE).proxyUrl ===
  112. "/api/cors/"
  113. ) {
  114. newState.proxyUrl = "";
  115. }
  116. }
  117. return newState as any;
  118. },
  119. },
  120. );