From fbd1547fea63681344af959ed01f4e29235d3397 Mon Sep 17 00:00:00 2001 From: BPower0036 <80090789+BPower0036@users.noreply.github.com> Date: Sat, 4 Jun 2022 09:13:45 +0000 Subject: [PATCH] Added back-up option --- src/_locales/de/messages.json | 60 ++++++++++++++++++++++++++ src/_locales/en/messages.json | 60 ++++++++++++++++++++++++++ src/_locales/en_CA/messages.json | 60 ++++++++++++++++++++++++++ src/_locales/en_GB/messages.json | 60 ++++++++++++++++++++++++++ src/_locales/nl/messages.json | 60 ++++++++++++++++++++++++++ src/js/background/backgroundLogic.js | 64 ++++++++++++++++++++++++++++ src/js/background/messageHandler.js | 6 +++ src/js/options.js | 57 ++++++++++++++++++++++++- src/manifest.json | 2 +- src/options.html | 17 ++++++++ src/update.json | 4 +- 11 files changed, 446 insertions(+), 4 deletions(-) diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index efc904e..b7b8b79 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -374,5 +374,65 @@ "themeDark": { "message": "Dunkel", "description": "Value for the theme option select tag" + }, + "backupFailure": { + "message": "Etwas ist schief gelaufen, Backup fehlgeschlagen: $errorMessage$", + "description": "(Options menu) When exporting containers as a file failed", + "placeholders": { + "errorMessage": { + "content": "$1" + } + } + }, + "containersRestored": { + "message": "$restoredCount$ wiederhergestellt.", + "description": "(Options menu) When importing containers from a file succeeded", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + } + } + }, + "containersPartiallyRestored": { + "message": "$restoredCount$ wiederhergestellt, aber einige verloren ihre Site-Zuordnung: $incompleteList$.", + "description": "(Options menu) When importing containers from a file succeeded, but some containers associations couldn’t be restored", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + }, + "incompleteList": { + "content": "$1", + "example": "MyFacebookContainer, MyTwitterContainer" + } + } + }, + "containersRestorationFailed": { + "message": "Die Datei ist beschädigt oder es ist keine Container-Sicherungsdatei.", + "description": "(Options menu) When importing containers from a file failed (most likely due to a malformed file)" + }, + "backup": { + "message": "Backup", + "description": "(Options menu) Backup to/from a file header" + }, + "restoreLegend": { + "message": "Wiederherstellen", + "description": "(Options menu) Restore containers from a file group header" + }, + "warningConfigOverride": { + "message": "WARNUNG! Dieser Vorgang löscht die aktuelle Konfiguration mit der importierten. Alle mit vorhandenen Containern verknüpften Cookies werden verworfen.", + "description": "(Options menu) Warns the user that old containers will be deleted along with their associated cookies" + }, + "saveLegend": { + "message": "Speichern", + "description": "(Options menu) Save containers to a file group header"}, + "saveButton": { + "message": "Speichern", + "description": "(Options menu) Validate saving containers to a file" + }, + "noteWontBackupCookies": { + "message": "Sichert nur Containernamen, Symbole, Farben und Site-Zuordnungen, aber KEINE Container-Cookies.", + "description": "(Options menu) Inform the user that cookies won’t be saved and they cannot be restored using the file backup" } } diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index ac8aa6e..0f55674 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -374,5 +374,65 @@ "themeDark": { "message": "Dark", "description": "Value for the theme option select tag" + }, + "backupFailure": { + "message": "Something went wrong, backup failed: $errorMessage$", + "description": "(Options menu) When exporting containers as a file failed", + "placeholders": { + "errorMessage": { + "content": "$1" + } + } + }, + "containersRestored": { + "message": "$restoredCount$ restored.", + "description": "(Options menu) When importing containers from a file succeeded", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + } + } + }, + "containersPartiallyRestored": { + "message": "$restoredCount$ restored, but some lost their site association: $incompleteList$.", + "description": "(Options menu) When importing containers from a file succeeded, but some containers associations couldn’t be restored", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + }, + "incompleteList": { + "content": "$1", + "example": "MyFacebookContainer, MyTwitterContainer" + } + } + }, + "containersRestorationFailed": { + "message": "The file is corrupted, or isn’t a container backup file.", + "description": "(Options menu) When importing containers from a file failed (most likely due to a malformed file)" + }, + "backup": { + "message": "Backup", + "description": "(Options menu) Backup to/from a file header" + }, + "restoreLegend": { + "message": "Restore", + "description": "(Options menu) Restore containers from a file group header" + }, + "warningConfigOverride": { + "message": "WARNING! This operation will erase current configuration with the imported one. All cookies associated with existing containers will be discarded.", + "description": "(Options menu) Warns the user that old containers will be deleted along with their associated cookies" + }, + "saveLegend": { + "message": "Save", + "description": "(Options menu) Save containers to a file group header"}, + "saveButton": { + "message": "Save", + "description": "(Options menu) Validate saving containers to a file" + }, + "noteWontBackupCookies": { + "message": "Backs up only containers names, icons, colors and site associations, but NOT containers’ cookies.", + "description": "(Options menu) Inform the user that cookies won’t be saved and they cannot be restored using the file backup" } } diff --git a/src/_locales/en_CA/messages.json b/src/_locales/en_CA/messages.json index 1d6e166..331b04d 100644 --- a/src/_locales/en_CA/messages.json +++ b/src/_locales/en_CA/messages.json @@ -374,5 +374,65 @@ "themeDark": { "message": "Dark", "description": "Value for the theme option select tag" + }, + "backupFailure": { + "message": "Something went wrong, backup failed: $errorMessage$", + "description": "(Options menu) When exporting containers as a file failed", + "placeholders": { + "errorMessage": { + "content": "$1" + } + } + }, + "containersRestored": { + "message": "$restoredCount$ restored.", + "description": "(Options menu) When importing containers from a file succeeded", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + } + } + }, + "containersPartiallyRestored": { + "message": "$restoredCount$ restored, but some lost their site association: $incompleteList$.", + "description": "(Options menu) When importing containers from a file succeeded, but some containers associations couldn’t be restored", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + }, + "incompleteList": { + "content": "$1", + "example": "MyFacebookContainer, MyTwitterContainer" + } + } + }, + "containersRestorationFailed": { + "message": "The file is corrupted, or isn’t a container backup file.", + "description": "(Options menu) When importing containers from a file failed (most likely due to a malformed file)" + }, + "backup": { + "message": "Backup", + "description": "(Options menu) Backup to/from a file header" + }, + "restoreLegend": { + "message": "Restore", + "description": "(Options menu) Restore containers from a file group header" + }, + "warningConfigOverride": { + "message": "WARNING! This operation will erase current configuration with the imported one. All cookies associated with existing containers will be discarded.", + "description": "(Options menu) Warns the user that old containers will be deleted along with their associated cookies" + }, + "saveLegend": { + "message": "Save", + "description": "(Options menu) Save containers to a file group header"}, + "saveButton": { + "message": "Save", + "description": "(Options menu) Validate saving containers to a file" + }, + "noteWontBackupCookies": { + "message": "Backs up only containers names, icons, colors and site associations, but NOT containers’ cookies.", + "description": "(Options menu) Inform the user that cookies won’t be saved and they cannot be restored using the file backup" } } diff --git a/src/_locales/en_GB/messages.json b/src/_locales/en_GB/messages.json index 5c4f1f2..73ea069 100644 --- a/src/_locales/en_GB/messages.json +++ b/src/_locales/en_GB/messages.json @@ -374,5 +374,65 @@ "themeDark": { "message": "Dark", "description": "Value for the theme option select tag" + }, + "backupFailure": { + "message": "Something went wrong, backup failed: $errorMessage$", + "description": "(Options menu) When exporting containers as a file failed", + "placeholders": { + "errorMessage": { + "content": "$1" + } + } + }, + "containersRestored": { + "message": "$restoredCount$ restored.", + "description": "(Options menu) When importing containers from a file succeeded", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + } + } + }, + "containersPartiallyRestored": { + "message": "$restoredCount$ restored, but some lost their site association: $incompleteList$.", + "description": "(Options menu) When importing containers from a file succeeded, but some containers associations couldn’t be restored", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + }, + "incompleteList": { + "content": "$1", + "example": "MyFacebookContainer, MyTwitterContainer" + } + } + }, + "containersRestorationFailed": { + "message": "The file is corrupted, or isn’t a container backup file.", + "description": "(Options menu) When importing containers from a file failed (most likely due to a malformed file)" + }, + "backup": { + "message": "Backup", + "description": "(Options menu) Backup to/from a file header" + }, + "restoreLegend": { + "message": "Restore", + "description": "(Options menu) Restore containers from a file group header" + }, + "warningConfigOverride": { + "message": "WARNING! This operation will erase current configuration with the imported one. All cookies associated with existing containers will be discarded.", + "description": "(Options menu) Warns the user that old containers will be deleted along with their associated cookies" + }, + "saveLegend": { + "message": "Save", + "description": "(Options menu) Save containers to a file group header"}, + "saveButton": { + "message": "Save", + "description": "(Options menu) Validate saving containers to a file" + }, + "noteWontBackupCookies": { + "message": "Backs up only containers names, icons, colors and site associations, but NOT containers’ cookies.", + "description": "(Options menu) Inform the user that cookies won’t be saved and they cannot be restored using the file backup" } } diff --git a/src/_locales/nl/messages.json b/src/_locales/nl/messages.json index b45e37d..3560e3d 100644 --- a/src/_locales/nl/messages.json +++ b/src/_locales/nl/messages.json @@ -374,5 +374,65 @@ "themeDark": { "message": "Donker", "description": "Value for the theme option select tag" + }, + "backupFailure": { + "message": "Er ging iets mis, back-up mislukte: $errorMessage$", + "description": "(Options menu) When exporting containers as a file failed", + "placeholders": { + "errorMessage": { + "content": "$1" + } + } + }, + "containersRestored": { + "message": "$restoredCount$ hersteld.", + "description": "(Options menu) When importing containers from a file succeeded", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + } + } + }, + "containersPartiallyRestored": { + "message": "$restoredCount$ hersteld, maar sommige verloren hun site-associatie: $incompleteList$.", + "description": "(Options menu) When importing containers from a file succeeded, but some containers associations couldn’t be restored", + "placeholders": { + "restoredCount": { + "content": "$1", + "example": "12" + }, + "incompleteList": { + "content": "$1", + "example": "MyFacebookContainer, MyTwitterContainer" + } + } + }, + "containersRestorationFailed": { + "message": "Het bestand is beschadigd of is geen containerback-upbestand.", + "description": "(Options menu) When importing containers from a file failed (most likely due to a malformed file)" + }, + "backup": { + "message": "Backup", + "description": "(Options menu) Backup to/from a file header" + }, + "restoreLegend": { + "message": "Restore", + "description": "(Options menu) Restore containers from a file group header" + }, + "warningConfigOverride": { + "message": "WAARSCHUWING! Met deze bewerking wordt de huidige configuratie met de geïmporteerde gewist. Alle cookies die aan bestaande containers zijn gekoppeld, worden verwijderd.", + "description": "(Options menu) Warns the user that old containers will be deleted along with their associated cookies" + }, + "saveLegend": { + "message": "Opslaan", + "description": "(Options menu) Save containers to a file group header"}, + "saveButton": { + "message": "Opslaan", + "description": "(Options menu) Validate saving containers to a file" + }, + "noteWontBackupCookies": { + "message": "Maakt alleen een back-up van namen, pictogrammen, kleuren en site-associaties van containers, maar NIET van containercookies.", + "description": "(Options menu) Inform the user that cookies won’t be saved and they cannot be restored using the file backup" } } diff --git a/src/js/background/backgroundLogic.js b/src/js/background/backgroundLogic.js index 1050d40..309cc1e 100644 --- a/src/js/background/backgroundLogic.js +++ b/src/js/background/backgroundLogic.js @@ -272,6 +272,70 @@ const backgroundLogic = { return browser.tabs.remove(tabIds); }, + async backupIdentitiesState() { + const identities = await browser.contextualIdentities.query({}); + return Promise.all( + identities.map(async ({ cookieStoreId, color, icon, name }) => { + const userContextId = this.getUserContextIdFromCookieStoreId(cookieStoreId); + const sitesByContainer = await assignManager.storageArea.getAssignedSites(userContextId); + const sites = Object.values(sitesByContainer).map(({ neverAsk, hostname }) => ({ neverAsk, hostname })); + return ({ color, icon, name, sites }); + }) + ); + }, + + async restoreIdentitiesState(identities) { + const backup = await browser.contextualIdentities.query({}); + const incomplete = []; + let allSucceed = true; + const identitiesPromise = identities.map(async ({ color, icon, name, sites }) => { + try { + if (typeof color !== "string" || typeof icon !== "string" || typeof name !== "string" || !Array.isArray((sites))) + throw new Error("Corrupted container backup"); + const identity = await browser.contextualIdentities.create({ color, icon, name }); + try { + await identityState.storageArea.get(identity.cookieStoreId); + const userContextId = this.getUserContextIdFromCookieStoreId(identity.cookieStoreId); + for (const { neverAsk, hostname } of sites) { + if (typeof neverAsk !== "boolean" || typeof hostname !== "string" || hostname === "") + throw new Error("Corrupted site association"); + const pageUrl = `http://${hostname}`; // protocol doesn't really matter here + await assignManager.storageArea.set(pageUrl, { + neverAsk, + userContextId + }); + } + } catch (err) { + incomplete.push(name); // site association damaged + } + return identity; + } catch (err) { + allSucceed = false; + return null; + } + }); + const created = await Promise.all(identitiesPromise); + if (!allSucceed) { // Importation failed, restore previous state + await Promise.all( + created.map(async (identityOrNull) => { + if (identityOrNull) { + await identityState.storageArea.remove(identityOrNull.cookieStoreId); + await browser.contextualIdentities.remove(identityOrNull.cookieStoreId); + } + }) + ); + throw new Error("Some containers couldn't be created"); + } else { // Importation succeed, remove old identities + await Promise.all( + backup.map(async (identity) => { + await identityState.storageArea.remove(identity.cookieStoreId); + await browser.contextualIdentities.remove(identity.cookieStoreId); + }) + ); + } + return { created: created.length, incomplete }; + }, + async queryIdentitiesState(windowId) { const identities = await browser.contextualIdentities.query({}); const identitiesOutput = {}; diff --git a/src/js/background/messageHandler.js b/src/js/background/messageHandler.js index 6e5417a..cb1d138 100644 --- a/src/js/background/messageHandler.js +++ b/src/js/background/messageHandler.js @@ -78,6 +78,12 @@ const messageHandler = { windowId: m.windowId }); break; + case "backupIdentitiesState": + response = backgroundLogic.backupIdentitiesState(); + break; + case "restoreIdentitiesState": + response = backgroundLogic.restoreIdentitiesState(m.identities); + break; case "queryIdentitiesState": response = backgroundLogic.queryIdentitiesState(m.message.windowId); break; diff --git a/src/js/options.js b/src/js/options.js index a32063c..067e5dc 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -65,6 +65,56 @@ async function changeTheme(event) { await browser.storage.local.set({currentThemeId: theme.selectedIndex}); } +async function backupContainers() { + const backupLink = document.getElementById("containers-save-link"); + const backupResult = document.getElementById("containers-save-result"); + try { + const content = JSON.stringify( + await browser.runtime.sendMessage({ + method: "backupIdentitiesState" + }) + ); + backupLink.href = `data:application/json;base64,${btoa(content)}`; + backupLink.download = `containers-backup-${(new Date()).toISOString()}.json`; + backupLink.click(); + backupResult.textContent = ""; + } catch (err) { + backupResult.textContent = browser.i18n.getMessage("backupFailure", [String(err.message || err)]); + backupResult.style.color = "red"; + } +} + +async function restoreContainers(event) { + const restoreInput = event.currentTarget; + const restoreResult = document.getElementById("containers-restore-result"); + event.preventDefault(); + if (restoreInput.files.length) { + const reader = new FileReader(); + reader.onloadend = async () => { + try { + const identitiesState = JSON.parse(reader.result); + const { created: restoredCount, incomplete } = await browser.runtime.sendMessage({ + method: "restoreIdentitiesState", + identities: identitiesState + }); + if (incomplete.length === 0) { + restoreResult.textContent = browser.i18n.getMessage("containersRestored", [String(restoredCount)]); + restoreResult.style.color = "green"; + } else { + restoreResult.textContent = browser.i18n.getMessage("containersPartiallyRestored", [String(restoredCount), String(incomplete.join(", "))]); + restoreResult.style.color = "orange"; + } + } catch (err) { + console.error("Cannot restore containers list: %s", err.message || err); + restoreResult.textContent = browser.i18n.getMessage("containersRestorationFailed"); + restoreResult.style.color = "red"; + } + }; + reader.readAsText(restoreInput.files.item(0)); + } + restoreInput.value = ""; +} + async function setupOptions() { const { syncEnabled } = await browser.storage.local.get("syncEnabled"); const { replaceTabEnabled } = await browser.storage.local.get("replaceTabEnabled"); @@ -133,6 +183,7 @@ document.querySelector("#syncCheck").addEventListener( "change", enableDisableSy document.querySelector("#replaceTabCheck").addEventListener( "change", enableDisableReplaceTab); document.querySelector("#pageActionCheck").addEventListener( "change", enableDisablePageAction); document.querySelector("#changeTheme").addEventListener( "change", changeTheme); +document.querySelector("#containersRestoreInput").addEventListener( "change", restoreContainers); maybeShowPermissionsWarningIcon(); for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { @@ -141,8 +192,12 @@ for (let i=0; i < NUMBER_OF_KEYBOARD_SHORTCUTS; i++) { } document.querySelectorAll("[data-btn-id]").forEach(btn => { - btn.addEventListener("click", () => { + btn.addEventListener("click", e => { switch (btn.dataset.btnId) { + case "containers-save-button": + e.preventDefault(); + backupContainers(); + break; case "reset-onboarding": resetOnboarding(); break; diff --git a/src/manifest.json b/src/manifest.json index 5e10094..9e26f8e 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 2, "author": "BPower0036", "name": "Multi-Account Containers (Wildcard)", - "version": "8.0.8.0", + "version": "8.0.8.1a2", "incognito": "not_allowed", "description": "__MSG_extensionDescription__", "icons": { diff --git a/src/options.html b/src/options.html index 5de86ab..80ea298 100644 --- a/src/options.html +++ b/src/options.html @@ -62,6 +62,23 @@ +

+
+
+ + +

+

+
+
+ + + +

+

+
+
+