mirror of
				https://github.com/TeamPiped/Piped.git
				synced 2024-08-14 23:57:27 +00:00 
			
		
		
		
	Merge a5c0d8007c into a055ae6686
				
					
				
			This commit is contained in:
		
						commit
						d26ce27691
					
				
					 9 changed files with 220 additions and 19 deletions
				
			
		| 
						 | 
					@ -21,6 +21,7 @@
 | 
				
			||||||
        "linkify-html": "4.1.1",
 | 
					        "linkify-html": "4.1.1",
 | 
				
			||||||
        "linkifyjs": "4.1.1",
 | 
					        "linkifyjs": "4.1.1",
 | 
				
			||||||
        "mux.js": "6.3.0",
 | 
					        "mux.js": "6.3.0",
 | 
				
			||||||
 | 
					        "pako": "2.1.0",
 | 
				
			||||||
        "qrcode": "^1.5.3",
 | 
					        "qrcode": "^1.5.3",
 | 
				
			||||||
        "shaka-player": "4.5.0",
 | 
					        "shaka-player": "4.5.0",
 | 
				
			||||||
        "stream-browserify": "3.0.0",
 | 
					        "stream-browserify": "3.0.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										7
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								pnpm-lock.yaml
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -38,6 +38,9 @@ dependencies:
 | 
				
			||||||
  mux.js:
 | 
					  mux.js:
 | 
				
			||||||
    specifier: 6.3.0
 | 
					    specifier: 6.3.0
 | 
				
			||||||
    version: 6.3.0
 | 
					    version: 6.3.0
 | 
				
			||||||
 | 
					  pako:
 | 
				
			||||||
 | 
					    specifier: 2.1.0
 | 
				
			||||||
 | 
					    version: 2.1.0
 | 
				
			||||||
  qrcode:
 | 
					  qrcode:
 | 
				
			||||||
    specifier: ^1.5.3
 | 
					    specifier: ^1.5.3
 | 
				
			||||||
    version: 1.5.3
 | 
					    version: 1.5.3
 | 
				
			||||||
| 
						 | 
					@ -4194,6 +4197,10 @@ packages:
 | 
				
			||||||
    engines: {node: '>=6'}
 | 
					    engines: {node: '>=6'}
 | 
				
			||||||
    dev: false
 | 
					    dev: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /pako@2.1.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
 | 
				
			||||||
 | 
					    dev: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /parent-module@1.0.1:
 | 
					  /parent-module@1.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
 | 
					    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
 | 
				
			||||||
    engines: {node: '>=6'}
 | 
					    engines: {node: '>=6'}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										65
									
								
								src/App.vue
									
										
									
									
									
								
							
							
						
						
									
										65
									
								
								src/App.vue
									
										
									
									
									
								
							| 
						 | 
					@ -9,7 +9,7 @@
 | 
				
			||||||
            </router-view>
 | 
					            </router-view>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <FooterComponent />
 | 
					        <FooterComponent :config="config" />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
| 
						 | 
					@ -27,6 +31,7 @@ export default {
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            theme: "dark",
 | 
					            theme: "dark",
 | 
				
			||||||
 | 
					            config: null,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    mounted() {
 | 
				
			||||||
| 
						 | 
					@ -35,6 +40,15 @@ export default {
 | 
				
			||||||
            this.setTheme();
 | 
					            this.setTheme();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.fetchJson(this.authApiUrl() + "/config")
 | 
				
			||||||
 | 
					            .then(config => {
 | 
				
			||||||
 | 
					                this.config = config;
 | 
				
			||||||
 | 
					                state.config = config;
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .then(() => {
 | 
				
			||||||
 | 
					                this.onConfigLoaded();
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ("indexedDB" in window) {
 | 
					        if ("indexedDB" in window) {
 | 
				
			||||||
            const request = indexedDB.open("piped-db", 5);
 | 
					            const request = indexedDB.open("piped-db", 5);
 | 
				
			||||||
            request.onupgradeneeded = ev => {
 | 
					            request.onupgradeneeded = ev => {
 | 
				
			||||||
| 
						 | 
					@ -115,6 +129,55 @@ export default {
 | 
				
			||||||
            const themeColor = document.querySelector("meta[name='theme-color']");
 | 
					            const themeColor = document.querySelector("meta[name='theme-color']");
 | 
				
			||||||
            themeColor.setAttribute("content", currentColor[this.theme]);
 | 
					            themeColor.setAttribute("content", currentColor[this.theme]);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        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>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,12 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script>
 | 
					<script>
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
 | 
					    props: {
 | 
				
			||||||
 | 
					        config: {
 | 
				
			||||||
 | 
					            type: Object,
 | 
				
			||||||
 | 
					            required: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            donationHref: null,
 | 
					            donationHref: null,
 | 
				
			||||||
| 
						 | 
					@ -36,16 +42,13 @@ export default {
 | 
				
			||||||
            privacyPolicyHref: null,
 | 
					            privacyPolicyHref: null,
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    mounted() {
 | 
					    watch: {
 | 
				
			||||||
        this.fetchConfig();
 | 
					        config: {
 | 
				
			||||||
 | 
					            handler() {
 | 
				
			||||||
 | 
					                this.donationHref = this.config?.donationUrl;
 | 
				
			||||||
 | 
					                this.statusPageHref = this.config?.statusPageUrl;
 | 
				
			||||||
 | 
					                this.privacyPolicyHref = this.config?.privacyPolicyUrl;
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
    methods: {
 | 
					 | 
				
			||||||
        async fetchConfig() {
 | 
					 | 
				
			||||||
            this.fetchJson(this.apiUrl() + "/config").then(config => {
 | 
					 | 
				
			||||||
                this.donationHref = config?.donationUrl;
 | 
					 | 
				
			||||||
                this.statusPageHref = config?.statusPageUrl;
 | 
					 | 
				
			||||||
                this.privacyPolicyHref = config?.privacyPolicyUrl;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -380,6 +380,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,
 | 
				
			||||||
| 
						 | 
					@ -614,6 +617,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();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								src/main.js
									
										
									
									
									
								
							
							
						
						
									
										16
									
								
								src/main.js
									
										
									
									
									
								
							| 
						 | 
					@ -125,10 +125,10 @@ const mixin = {
 | 
				
			||||||
                if (!disableAlert) alert(this.$t("info.local_storage"));
 | 
					                if (!disableAlert) alert(this.$t("info.local_storage"));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        getPreferenceBoolean(key, defaultVal) {
 | 
					        getPreferenceBoolean(key, defaultVal, allowQuery = true) {
 | 
				
			||||||
            var value;
 | 
					            var value;
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
                (value = new URLSearchParams(window.location.search).get(key)) !== null ||
 | 
					                (allowQuery && (value = new URLSearchParams(window.location.search).get(key)) !== null) ||
 | 
				
			||||||
                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
					                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                switch (String(value).toLowerCase()) {
 | 
					                switch (String(value).toLowerCase()) {
 | 
				
			||||||
| 
						 | 
					@ -142,29 +142,29 @@ const mixin = {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else return defaultVal;
 | 
					            } else return defaultVal;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        getPreferenceString(key, defaultVal) {
 | 
					        getPreferenceString(key, defaultVal, allowQuery = true) {
 | 
				
			||||||
            var value;
 | 
					            var value;
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
                (value = new URLSearchParams(window.location.search).get(key)) !== null ||
 | 
					                (allowQuery && (value = new URLSearchParams(window.location.search).get(key)) !== null) ||
 | 
				
			||||||
                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
					                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                return value;
 | 
					                return value;
 | 
				
			||||||
            } else return defaultVal;
 | 
					            } else return defaultVal;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        getPreferenceNumber(key, defaultVal) {
 | 
					        getPreferenceNumber(key, defaultVal, allowQuery = true) {
 | 
				
			||||||
            var value;
 | 
					            var value;
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
                (value = new URLSearchParams(window.location.search).get(key)) !== null ||
 | 
					                (allowQuery && (value = new URLSearchParams(window.location.search).get(key)) !== null) ||
 | 
				
			||||||
                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
					                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                const num = Number(value);
 | 
					                const num = Number(value);
 | 
				
			||||||
                return isNaN(num) ? defaultVal : num;
 | 
					                return isNaN(num) ? defaultVal : num;
 | 
				
			||||||
            } else return defaultVal;
 | 
					            } else return defaultVal;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        getPreferenceJSON(key, defaultVal) {
 | 
					        getPreferenceJSON(key, defaultVal, allowQuery = true) {
 | 
				
			||||||
            var value;
 | 
					            var value;
 | 
				
			||||||
            if (
 | 
					            if (
 | 
				
			||||||
                (value = new URLSearchParams(window.location.search).get(key)) !== null ||
 | 
					                (allowQuery && (value = new URLSearchParams(window.location.search).get(key)) !== null) ||
 | 
				
			||||||
                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
					                (this.testLocalStorage && (value = localStorage.getItem(key)) !== null)
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                return JSON.parse(value);
 | 
					                return JSON.parse(value);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										38
									
								
								src/utils/compressionUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/utils/compressionUtils.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,38 @@
 | 
				
			||||||
 | 
					export const compressGzip = async data => {
 | 
				
			||||||
 | 
					    // Firefox does not support CompressionStream yet
 | 
				
			||||||
 | 
					    if (typeof CompressionStream !== "undefined") {
 | 
				
			||||||
 | 
					        let bytes = new TextEncoder().encode(data);
 | 
				
			||||||
 | 
					        // eslint-disable-next-line no-undef
 | 
				
			||||||
 | 
					        const cs = new CompressionStream("gzip");
 | 
				
			||||||
 | 
					        const writer = cs.writable.getWriter();
 | 
				
			||||||
 | 
					        writer.write(bytes);
 | 
				
			||||||
 | 
					        writer.close();
 | 
				
			||||||
 | 
					        const compAb = await new Response(cs.readable).arrayBuffer();
 | 
				
			||||||
 | 
					        bytes = new Uint8Array(compAb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return bytes;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const pako = await import("pako");
 | 
				
			||||||
 | 
					        return pako.gzip(data);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const decompressGzip = async compressedData => {
 | 
				
			||||||
 | 
					    // Firefox does not support DecompressionStream yet
 | 
				
			||||||
 | 
					    if (typeof DecompressionStream !== "undefined") {
 | 
				
			||||||
 | 
					        // eslint-disable-next-line no-undef
 | 
				
			||||||
 | 
					        const ds = new DecompressionStream("gzip");
 | 
				
			||||||
 | 
					        const writer = ds.writable.getWriter();
 | 
				
			||||||
 | 
					        writer.write(compressedData);
 | 
				
			||||||
 | 
					        writer.close();
 | 
				
			||||||
 | 
					        const decompAb = await new Response(ds.readable).arrayBuffer();
 | 
				
			||||||
 | 
					        const bytes = new Uint8Array(decompAb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return new TextDecoder().decode(bytes);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const pako = await import("pako");
 | 
				
			||||||
 | 
					        const inflated = pako.inflate(compressedData, { to: "string" });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return inflated;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/utils/encryptionUtils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/utils/encryptionUtils.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					// These functions accept and return Uint8Arrays
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function encryptAESGCM(plaintext, key) {
 | 
				
			||||||
 | 
					    const iv = crypto.getRandomValues(new Uint8Array(12));
 | 
				
			||||||
 | 
					    const algorithm = { name: "AES-GCM", iv: iv };
 | 
				
			||||||
 | 
					    const keyMaterial = await crypto.subtle.importKey("raw", key, algorithm, false, ["encrypt"]);
 | 
				
			||||||
 | 
					    const ciphertext = await crypto.subtle.encrypt(algorithm, keyMaterial, plaintext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Uint8Array([...iv, ...new Uint8Array(ciphertext)]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function decryptAESGCM(ciphertextArray, key) {
 | 
				
			||||||
 | 
					    const iv = new Uint8Array(ciphertextArray.slice(0, 12));
 | 
				
			||||||
 | 
					    const algorithm = { name: "AES-GCM", iv: iv };
 | 
				
			||||||
 | 
					    const keyMaterial = await crypto.subtle.importKey("raw", key, algorithm, false, ["decrypt"]);
 | 
				
			||||||
 | 
					    const decrypted = await crypto.subtle.decrypt(algorithm, keyMaterial, new Uint8Array(ciphertextArray.slice(12)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return decrypted;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function generateKey() {
 | 
				
			||||||
 | 
					    const algorithm = { name: "AES-GCM", length: 256 };
 | 
				
			||||||
 | 
					    const key = await crypto.subtle.generateKey(algorithm, true, ["encrypt", "decrypt"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return await crypto.subtle.exportKey("raw", key);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function encodeArrayToBase64(array) {
 | 
				
			||||||
 | 
					    const chars = String.fromCharCode.apply(null, array);
 | 
				
			||||||
 | 
					    return btoa(chars);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function decodeBase64ToArray(base64) {
 | 
				
			||||||
 | 
					    const chars = atob(base64);
 | 
				
			||||||
 | 
					    return new Uint8Array(chars.split("").map(c => c.charCodeAt(0)));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/utils/store.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/utils/store.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					import { reactive } from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const state = reactive({});
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue