diff options
author | Lewis Crichton <lewi@lewisakura.moe> | 2023-04-07 01:27:18 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-07 02:27:18 +0200 |
commit | 97f8d4d5154d566568fc475d6aaba5db07399b2b (patch) | |
tree | feafd22c26bdfc37a2d787e60f52bec94e777636 /src/utils/settingsSync.ts | |
parent | 2672dea8e361e8216b9459ac5dac97d36a47e412 (diff) | |
download | Vencord-97f8d4d5154d566568fc475d6aaba5db07399b2b.tar.gz Vencord-97f8d4d5154d566568fc475d6aaba5db07399b2b.tar.bz2 Vencord-97f8d4d5154d566568fc475d6aaba5db07399b2b.zip |
feat: Cloud settings sync (#505)
Co-authored-by: Ven <vendicated@riseup.net>
Diffstat (limited to 'src/utils/settingsSync.ts')
-rw-r--r-- | src/utils/settingsSync.ts | 191 |
1 files changed, 181 insertions, 10 deletions
diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index 781899f..d59787d 100644 --- a/src/utils/settingsSync.ts +++ b/src/utils/settingsSync.ts @@ -16,8 +16,12 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { showNotification } from "@api/Notifications"; +import { PlainSettings, Settings } from "@api/settings"; import { Toasts } from "@webpack/common"; +import { deflateSync, inflateSync } from "fflate"; +import { getCloudAuth, getCloudUrl } from "./cloud"; import IpcEvents from "./IpcEvents"; import Logger from "./Logger"; @@ -64,17 +68,18 @@ export async function downloadSettingsBackup() { } } -const toastSuccess = () => Toasts.show({ - type: Toasts.Type.SUCCESS, - message: "Settings successfully imported. Restart to apply changes!", - id: Toasts.genId() -}); +const toast = (type: number, message: string) => + Toasts.show({ + type, + message, + id: Toasts.genId() + }); -const toastFailure = (err: any) => Toasts.show({ - type: Toasts.Type.FAILURE, - message: `Failed to import settings: ${String(err)}`, - id: Toasts.genId() -}); +const toastSuccess = () => + toast(Toasts.Type.SUCCESS, "Settings successfully imported. Restart to apply changes!"); + +const toastFailure = (err: any) => + toast(Toasts.Type.FAILURE, `Failed to import settings: ${String(err)}`); export async function uploadSettingsBackup(showToast = true): Promise<void> { if (IS_DISCORD_DESKTOP) { @@ -121,3 +126,169 @@ export async function uploadSettingsBackup(showToast = true): Promise<void> { setImmediate(() => document.body.removeChild(input)); } } + +// Cloud settings +const cloudSettingsLogger = new Logger("Cloud:Settings", "#39b7e0"); + +export async function putCloudSettings() { + const settings = await exportSettings(); + + try { + const res = await fetch(new URL("/v1/settings", getCloudUrl()), { + method: "PUT", + headers: new Headers({ + Authorization: await getCloudAuth(), + "Content-Type": "application/octet-stream" + }), + body: deflateSync(new TextEncoder().encode(settings)) + }); + + if (!res.ok) { + cloudSettingsLogger.error(`Failed to sync up, API returned ${res.status}`); + showNotification({ + title: "Cloud Settings", + body: `Could not synchronize settings to cloud (API returned ${res.status}).`, + color: "var(--red-360)" + }); + return; + } + + const { written } = await res.json(); + PlainSettings.cloud.settingsSyncVersion = written; + VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(PlainSettings, null, 4)); + + cloudSettingsLogger.info("Settings uploaded to cloud successfully"); + showNotification({ + title: "Cloud Settings", + body: "Synchronized your settings to the cloud!", + color: "var(--green-360)" + }); + } catch (e: any) { + cloudSettingsLogger.error("Failed to sync up", e); + showNotification({ + title: "Cloud Settings", + body: `Could not synchronize settings to the cloud (${e.toString()}).`, + color: "var(--red-360)" + }); + } +} + +export async function getCloudSettings(shouldNotify = true, force = false) { + try { + const res = await fetch(new URL("/v1/settings", getCloudUrl()), { + method: "GET", + headers: new Headers({ + Authorization: await getCloudAuth(), + Accept: "application/octet-stream", + "If-None-Match": Settings.cloud.settingsSyncVersion.toString() + }), + }); + + if (res.status === 404) { + cloudSettingsLogger.info("No settings on the cloud"); + if (shouldNotify) + showNotification({ + title: "Cloud Settings", + body: "There are no settings in the cloud." + }); + return false; + } + + if (res.status === 304) { + cloudSettingsLogger.info("Settings up to date"); + if (shouldNotify) + showNotification({ + title: "Cloud Settings", + body: "Your settings are up to date." + }); + return false; + } + + if (!res.ok) { + cloudSettingsLogger.error(`Failed to sync down, API returned ${res.status}`); + showNotification({ + title: "Cloud Settings", + body: `Could not synchronize settings from the cloud (API returned ${res.status}).`, + color: "var(--red-360)" + }); + return false; + } + + const written = Number(res.headers.get("etag")!); + const localWritten = Settings.cloud.settingsSyncVersion; + + // don't need to check for written > localWritten because the server will return 304 due to if-none-match + if (!force && written < localWritten) { + if (shouldNotify) + showNotification({ + title: "Cloud Settings", + body: "Your local settings are newer than the cloud ones." + }); + return; + } + + const data = await res.arrayBuffer(); + + const settings = new TextDecoder().decode(inflateSync(new Uint8Array(data))); + await importSettings(settings); + + // sync with server timestamp instead of local one + PlainSettings.cloud.settingsSyncVersion = written; + VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(PlainSettings, null, 4)); + + cloudSettingsLogger.info("Settings loaded from cloud successfully"); + if (shouldNotify) + showNotification({ + title: "Cloud Settings", + body: "Your settings have been updated! Click here to restart to fully apply changes!", + color: "var(--green-360)", + onClick: () => window.DiscordNative.app.relaunch() + }); + + return true; + } catch (e: any) { + cloudSettingsLogger.error("Failed to sync down", e); + showNotification({ + title: "Cloud Settings", + body: `Could not synchronize settings from the cloud (${e.toString()}).`, + color: "var(--red-360)" + }); + + return false; + } +} + +export async function deleteCloudSettings() { + try { + const res = await fetch(new URL("/v1/settings", getCloudUrl()), { + method: "DELETE", + headers: new Headers({ + Authorization: await getCloudAuth() + }), + }); + + if (!res.ok) { + cloudSettingsLogger.error(`Failed to delete, API returned ${res.status}`); + showNotification({ + title: "Cloud Settings", + body: `Could not delete settings (API returned ${res.status}).`, + color: "var(--red-360)" + }); + return; + } + + cloudSettingsLogger.info("Settings deleted from cloud successfully"); + showNotification({ + title: "Cloud Settings", + body: "Settings deleted from cloud!", + color: "var(--green-360)" + }); + } catch (e: any) { + cloudSettingsLogger.error("Failed to delete", e); + showNotification({ + title: "Cloud Settings", + body: `Could not delete settings (${e.toString()}).`, + color: "var(--red-360)" + }); + } +} |