Add support to sync preferences.

This commit is contained in:
Kavin 2023-02-20 15:43:25 +00:00
parent 2d7801eb67
commit ee20e0ccdf
No known key found for this signature in database
GPG Key ID: 6E4598CA5C92C41F
4 changed files with 107 additions and 8 deletions

View File

@ -19,6 +19,10 @@ import FooterComponent from "./components/FooterComponent.vue";
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)"); const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)");
import { generateKey, encodeArrayToBase64, decodeBase64ToArray, decryptAESGCM } from "./utils/encryptionUtils";
import { decompressGzip } from "./utils/compressionUtils";
import { state } from "./utils/store";
export default { export default {
components: { components: {
NavBar, NavBar,
@ -39,6 +43,7 @@ export default {
this.fetchJson(this.authApiUrl() + "/config") this.fetchJson(this.authApiUrl() + "/config")
.then(config => { .then(config => {
this.config = config; this.config = config;
state.config = config;
}) })
.then(() => { .then(() => {
this.onConfigLoaded(); this.onConfigLoaded();
@ -120,6 +125,55 @@ export default {
const root = document.querySelector(":root"); const root = document.querySelector(":root");
this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark"); this.theme == "dark" ? root.classList.add("dark") : root.classList.remove("dark");
}, },
async onConfigLoaded() {
if (this.config.s3Enabled && this.authenticated) {
if (this.getPreferenceBoolean("syncPreferences", false, false)) {
var e2e2_b64_key = this.getPreferenceString("e2ee_key", null, false);
if (!e2e2_b64_key) {
const key = new Uint8Array(await generateKey());
const encoded = encodeArrayToBase64(key);
this.setPreference("e2ee_key", encoded);
e2e2_b64_key = encoded;
}
const statResult = await this.fetchJson(
this.authApiUrl() + "/storage/stat",
{
file: "pipedpref",
},
{
headers: {
Authorization: this.getAuthToken(),
},
},
);
if (statResult.status === "exists") {
const data = await fetch(this.authApiUrl() + "/storage/get?file=pipedpref", {
method: "GET",
headers: {
Authorization: this.getAuthToken(),
},
}).then(resp => resp.arrayBuffer());
const cryptoKey = decodeBase64ToArray(e2e2_b64_key).buffer;
const decrypted = await decryptAESGCM(data, cryptoKey);
const decompressed = await decompressGzip(new Uint8Array(decrypted));
const localStorageJson = JSON.parse(decompressed);
// import into localStorage
for (var key in localStorageJson) {
if (Object.prototype.hasOwnProperty.call(localStorageJson, key)) {
localStorage[key] = localStorageJson[key];
}
}
}
}
}
},
}, },
}; };
</script> </script>

View File

@ -29,15 +29,12 @@
<script> <script>
export default { export default {
<<<<<<< Updated upstream
=======
props: { props: {
config: { config: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
>>>>>>> Stashed changes
data() { data() {
return { return {
donationHref: null, donationHref: null,
@ -45,11 +42,6 @@ export default {
privacyPolicyHref: null, privacyPolicyHref: null,
}; };
}, },
props: {
config: {
type: Object,
},
},
watch: { watch: {
config: { config: {
handler() { handler() {

View File

@ -379,6 +379,9 @@
<script> <script>
import CountryMap from "@/utils/CountryMaps/en.json"; import CountryMap from "@/utils/CountryMaps/en.json";
import ConfirmModal from "./ConfirmModal.vue"; import ConfirmModal from "./ConfirmModal.vue";
import { state } from "../utils/store";
import { encryptAESGCM, decodeBase64ToArray } from "../utils/encryptionUtils";
import { compressGzip } from "../utils/compressionUtils";
export default { export default {
components: { components: {
ConfirmModal, ConfirmModal,
@ -612,6 +615,53 @@ export default {
localStorage.setItem("hideWatched", this.hideWatched); localStorage.setItem("hideWatched", this.hideWatched);
localStorage.setItem("mobileChapterLayout", this.mobileChapterLayout); localStorage.setItem("mobileChapterLayout", this.mobileChapterLayout);
const config = state.config;
const key = this.getPreferenceString("e2ee_key", null, false);
if (config.s3Enabled && this.authenticated && key) {
const statResult = await this.fetchJson(
this.authApiUrl() + "/storage/stat",
{
file: "pipedpref",
},
{
headers: {
Authorization: this.getAuthToken(),
},
},
);
const etag = statResult.etag;
// export localStorage to JSON
const localStorageJson = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
localStorageJson[key] = localStorage.getItem(key);
}
const importedKey = decodeBase64ToArray(key).buffer;
const data = await compressGzip(JSON.stringify(localStorageJson));
const encrypted = await encryptAESGCM(data, importedKey);
await this.fetchJson(
this.authApiUrl() + "/storage/put",
{},
{
method: "POST",
headers: {
Authorization: this.getAuthToken(),
"x-file-name": "pipedpref",
"x-last-etag": etag,
},
body: encrypted,
},
);
}
if (shouldReload) window.location.reload(); if (shouldReload) window.location.reload();
} }
}, },

3
src/utils/store.js Normal file
View File

@ -0,0 +1,3 @@
import { reactive } from "vue";
export const state = reactive({});