aboutsummaryrefslogtreecommitdiff
path: root/src/utils/settingsSync.ts
diff options
context:
space:
mode:
authorLewis Crichton <lewi@lewisakura.moe>2023-04-07 01:27:18 +0100
committerGitHub <noreply@github.com>2023-04-07 02:27:18 +0200
commit97f8d4d5154d566568fc475d6aaba5db07399b2b (patch)
treefeafd22c26bdfc37a2d787e60f52bec94e777636 /src/utils/settingsSync.ts
parent2672dea8e361e8216b9459ac5dac97d36a47e412 (diff)
downloadVencord-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.ts191
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)"
+ });
+ }
+}