2022-11-21 19:25:40 +01:00
/ *
2025-11-16 15:17:35 +01:00
* Vencord , a Discord client mod
* Copyright ( c ) 2025 Vendicated and contributors
* SPDX - License - Identifier : GPL - 3.0 - or - later
* /
2022-11-21 19:25:40 +01:00
2023-04-07 01:27:18 +01:00
import { showNotification } from "@api/Notifications" ;
2023-05-06 01:36:00 +02:00
import { PlainSettings , Settings } from "@api/Settings" ;
2025-11-16 15:17:35 +01:00
import { Logger } from "@utils/Logger" ;
import { relaunch } from "@utils/native" ;
2025-11-18 23:48:00 -05:00
import { SettingsRouter } from "@webpack/common" ;
2023-04-07 01:27:18 +01:00
import { deflateSync , inflateSync } from "fflate" ;
2022-11-28 13:37:55 +01:00
2025-11-18 23:48:00 -05:00
import { deauthorizeCloud , getCloudAuth , getCloudUrl } from "./cloudSetup" ;
2025-11-16 15:17:35 +01:00
import { exportSettings , importSettings } from "./offline" ;
2022-11-21 19:25:40 +01:00
2025-11-16 15:17:35 +01:00
const logger = new Logger ( "SettingsSync:Cloud" , "#39b7e0" ) ;
2023-04-07 01:27:18 +01:00
2023-07-04 23:59:42 +08:00
export async function putCloudSettings ( manual? : boolean ) {
2025-12-04 12:07:19 -05:00
const settings = await exportSettings ( { syncDataStore : false , minify : true } ) ;
2023-04-07 01:27:18 +01:00
try {
const res = await fetch ( new URL ( "/v1/settings" , getCloudUrl ( ) ) , {
method : "PUT" ,
2024-02-29 16:26:32 -08:00
headers : {
2023-04-07 01:27:18 +01:00
Authorization : await getCloudAuth ( ) ,
"Content-Type" : "application/octet-stream"
2024-02-29 16:26:32 -08:00
} ,
2025-11-16 15:17:35 +01:00
body : deflateSync ( new TextEncoder ( ) . encode ( settings ) ) as Uint8Array < ArrayBuffer >
2023-04-07 01:27:18 +01:00
} ) ;
if ( ! res . ok ) {
2025-11-16 15:17:35 +01:00
logger . error ( ` Failed to sync up, API returned ${ res . status } ` ) ;
2023-04-07 01:27:18 +01:00
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 ;
2024-03-13 21:45:45 +01:00
VencordNative . settings . set ( PlainSettings ) ;
2023-04-07 01:27:18 +01:00
2025-11-16 15:17:35 +01:00
logger . info ( "Settings uploaded to cloud successfully" ) ;
2023-07-04 23:59:42 +08:00
if ( manual ) {
showNotification ( {
title : "Cloud Settings" ,
body : "Synchronized settings to the cloud!" ,
noPersist : true ,
} ) ;
}
2023-04-07 01:27:18 +01:00
} catch ( e : any ) {
2025-11-16 15:17:35 +01:00
logger . error ( "Failed to sync up" , e ) ;
2023-04-07 01:27:18 +01:00
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" ,
2024-02-29 16:26:32 -08:00
headers : {
2023-04-07 01:27:18 +01:00
Authorization : await getCloudAuth ( ) ,
Accept : "application/octet-stream" ,
"If-None-Match" : Settings . cloud . settingsSyncVersion . toString ( )
2024-02-29 16:26:32 -08:00
} ,
2023-04-07 01:27:18 +01:00
} ) ;
2025-11-18 23:48:00 -05:00
if ( res . status === 401 ) {
// User switched to an account that isn't connected to cloud
showNotification ( {
title : "Cloud Settings" ,
body : "Cloud sync was disabled because this account isn't connected to the cloud App. You can enable it again by connecting this account in Cloud Settings. (note: it will store your preferences separately)" ,
color : "var(--yellow-360)" ,
onClick : ( ) = > SettingsRouter . open ( "VencordCloud" )
} ) ;
// Disable cloud sync globally
Settings . cloud . authenticated = false ;
return false ;
}
2023-04-07 01:27:18 +01:00
if ( res . status === 404 ) {
2025-11-16 15:17:35 +01:00
logger . info ( "No settings on the cloud" ) ;
2023-04-07 01:27:18 +01:00
if ( shouldNotify )
showNotification ( {
title : "Cloud Settings" ,
2023-04-13 19:15:36 +02:00
body : "There are no settings in the cloud." ,
noPersist : true
2023-04-07 01:27:18 +01:00
} ) ;
return false ;
}
if ( res . status === 304 ) {
2025-11-16 15:17:35 +01:00
logger . info ( "Settings up to date" ) ;
2023-04-07 01:27:18 +01:00
if ( shouldNotify )
showNotification ( {
title : "Cloud Settings" ,
2023-04-13 19:15:36 +02:00
body : "Your settings are up to date." ,
noPersist : true
2023-04-07 01:27:18 +01:00
} ) ;
return false ;
}
if ( ! res . ok ) {
2025-11-16 15:17:35 +01:00
logger . error ( ` Failed to sync down, API returned ${ res . status } ` ) ;
2023-04-07 01:27:18 +01:00
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" ,
2023-04-13 19:15:36 +02:00
body : "Your local settings are newer than the cloud ones." ,
noPersist : true ,
2023-04-07 01:27:18 +01:00
} ) ;
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 ;
2024-03-13 21:45:45 +01:00
VencordNative . settings . set ( PlainSettings ) ;
2023-04-07 01:27:18 +01:00
2025-11-16 15:17:35 +01:00
logger . info ( "Settings loaded from cloud successfully" ) ;
2023-04-07 01:27:18 +01:00
if ( shouldNotify )
showNotification ( {
title : "Cloud Settings" ,
body : "Your settings have been updated! Click here to restart to fully apply changes!" ,
color : "var(--green-360)" ,
2023-09-24 01:16:41 +02:00
onClick : IS_WEB ? ( ) = > location . reload ( ) : relaunch ,
2023-04-13 19:15:36 +02:00
noPersist : true
2023-04-07 01:27:18 +01:00
} ) ;
return true ;
} catch ( e : any ) {
2025-11-16 15:17:35 +01:00
logger . error ( "Failed to sync down" , e ) ;
2023-04-07 01:27:18 +01:00
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" ,
2024-02-29 16:26:32 -08:00
headers : { Authorization : await getCloudAuth ( ) } ,
2023-04-07 01:27:18 +01:00
} ) ;
if ( ! res . ok ) {
2025-11-16 15:17:35 +01:00
logger . error ( ` Failed to delete, API returned ${ res . status } ` ) ;
2023-04-07 01:27:18 +01:00
showNotification ( {
title : "Cloud Settings" ,
body : ` Could not delete settings (API returned ${ res . status } ). ` ,
color : "var(--red-360)"
} ) ;
return ;
}
2025-11-16 15:17:35 +01:00
logger . info ( "Settings deleted from cloud successfully" ) ;
2023-04-07 01:27:18 +01:00
showNotification ( {
title : "Cloud Settings" ,
body : "Settings deleted from cloud!" ,
color : "var(--green-360)"
} ) ;
} catch ( e : any ) {
2025-11-16 15:17:35 +01:00
logger . error ( "Failed to delete" , e ) ;
2023-04-07 01:27:18 +01:00
showNotification ( {
title : "Cloud Settings" ,
body : ` Could not delete settings ( ${ e . toString ( ) } ). ` ,
color : "var(--red-360)"
} ) ;
}
}
2025-11-16 15:17:35 +01:00
export async function eraseAllCloudData() {
const res = await fetch ( new URL ( "/v1/" , getCloudUrl ( ) ) , {
method : "DELETE" ,
headers : { Authorization : await getCloudAuth ( ) }
} ) ;
if ( ! res . ok ) {
logger . error ( ` Failed to erase data, API returned ${ res . status } ` ) ;
showNotification ( {
title : "Cloud Integrations" ,
body : ` Could not erase all data (API returned ${ res . status } ), please contact support. ` ,
color : "var(--red-360)"
} ) ;
return ;
}
Settings . cloud . authenticated = false ;
await deauthorizeCloud ( ) ;
showNotification ( {
title : "Cloud Integrations" ,
body : "Successfully erased all data." ,
color : "var(--green-360)"
} ) ;
}