diff --git a/src/js/background/identityState.js b/src/js/background/identityState.js index 850ee50..d032790 100644 --- a/src/js/background/identityState.js +++ b/src/js/background/identityState.js @@ -39,19 +39,37 @@ const identityState = { const storeKey = this.getContainerStoreKey(cookieStoreId); return await this.area.remove([storeKey]); }, + + /* + * Looks for abandoned identities keys in local storage, and makes sure all + * identities registered in the browser are also in local storage. (this + * appears to not always be the case based on how this.get() is written) + */ async cleanup() { const identitiesList = await browser.contextualIdentities.query({}); const macConfigs = await this.area.get(); for(const configKey of Object.keys(macConfigs)) { if (configKey.includes("identitiesState@@_")) { const cookieStoreId = String(configKey).replace(/^identitiesState@@_/, ""); - const match = identitiesList.find(localIdentity => localIdentity.cookieStoreId === cookieStoreId); - if (!match && cookieStoreId !== "firefox-default") { - console.log("removed ", cookieStoreId, " from storage list"); + const match = identitiesList.find( + localIdentity => localIdentity.cookieStoreId === cookieStoreId + ); + if (cookieStoreId === "firefox-default") continue; + if (!match) { this.remove(cookieStoreId); + continue; + } + if (!macConfigs[configKey].macAddonUUID) { + await identityState.addUUID(cookieStoreId); } } } + + for (const identity of identitiesList) { + // ensure all identities have an entry in local storage + const data = await this.get(identity.cookieStoreId); + await this.set(identity.cookieStoreId, data); + } } }, diff --git a/src/js/background/sync.js b/src/js/background/sync.js index 41497bf..ff01e82 100644 --- a/src/js/background/sync.js +++ b/src/js/background/sync.js @@ -1,4 +1,4 @@ -const SYNC_DEBUG = false; +const SYNC_DEBUG = true; const sync = { storageArea: { @@ -39,28 +39,25 @@ const sync = { async backup(options) { if (SYNC_DEBUG) console.log("backup"); // remove listeners to avoid an infinite loop! - browser.storage.onChanged.removeListener( + await browser.storage.onChanged.removeListener( sync.storageArea.onChangedListener); - removeContextualIdentityListeners(); - try { - await updateSyncIdentities(); - await updateCookieStoreIdMap(); - await updateSyncSiteAssignments(); - if (options && options.uuid) - await updateDeletedIdentityList(options.uuid); - if (options && options.siteStoreKey) - await addToDeletedSitesList(options.siteStoreKey); - if (options && options.undelete) - await removeFromDeletedSitesList(options.undelete); - - if (SYNC_DEBUG) { - const storage = await sync.storageArea.get(); - console.log("in sync: ", storage); - const localStorage = await browser.storage.local.get(); - console.log("inLocal:", localStorage); - } - } catch (error) { - console.error("Error backing up", error); + + await removeContextualIdentityListeners(); + + await updateSyncIdentities(); + await updateCookieStoreIdMap(); + await updateSyncSiteAssignments(); + if (options && options.uuid) + await updateDeletedIdentityList(options.uuid); + if (options && options.siteStoreKey) + await addToDeletedSitesList(options.siteStoreKey); + if (options && options.undelete) + await removeFromDeletedSitesList(options.undelete); + if (SYNC_DEBUG) { + const storage = await sync.storageArea.get(); + console.log("in sync: ", storage); + const localStorage = await browser.storage.local.get(); + console.log("inLocal:", localStorage); } browser.storage.onChanged.addListener( sync.storageArea.onChangedListener); @@ -113,6 +110,10 @@ const sync = { } }, + /* + * Ensures all sync info matches. But maybe we shouldn't even use + * sync info that doesn't match. + */ async cleanup() { console.log("cleanupSync"); browser.storage.onChanged.removeListener( @@ -180,12 +181,38 @@ const sync = { sync.init(); +async function runSync() { + browser.storage.onChanged.removeListener( + sync.storageArea.onChangedListener); + removeContextualIdentityListeners(); + console.log("runSync"); + await identityState.storageArea.cleanup(); + + + if (await sync.storageArea.hasSyncStorage()){ + await sync.storageArea.cleanup(); + console.log("storage found, attempting to restore ..."); + await restore(); + return; + } + console.log("no sync storage, backing up..."); + await sync.storageArea.backup(); + return; +} + +async function restore() { + console.log("restore"); + await reconcileIdentitiesByUUID(); + await reconcileSiteAssignments(); + await sync.storageArea.backup(); +} + async function runFirstSync() { console.log("runFirstSync"); + // looks for abandoned identities keys in local storage, and identities + // not in localstorage (which also adds a uuid) await identityState.storageArea.cleanup(); - const localIdentities = await browser.contextualIdentities.query({}); - await addUUIDsToContainers(localIdentities); - // const inSync = await sync.storageArea.get(); + if (await sync.storageArea.hasSyncStorage()){ await sync.storageArea.cleanup(); console.log("storage found, attempting to restore ..."); @@ -197,17 +224,11 @@ async function runFirstSync() { await assignManager.storageArea.setSynced(); } -async function addUUIDsToContainers(localIdentities) { - for (const identity of localIdentities) { - await identityState.addUUID(identity.cookieStoreId); - } -} - async function restoreFirstRun() { console.log("restoreFirstRun"); - await reconcileIdentitiesByName(); + await reconcileIdentities(); await reconcileSiteAssignments(); - sync.storageArea.backup(); + await sync.storageArea.backup(); } /* @@ -215,8 +236,21 @@ async function restoreFirstRun() { * same container, and the color and icon are overwritten from sync, if * different. */ -async function reconcileIdentitiesByName(){ - console.log("reconcileIdentitiesByName"); +async function reconcileIdentities(){ + console.log("reconcileIdentities"); + + // first delete any from the deleted list + const deletedIdentityList = + await sync.storageArea.getStoredArray("deletedIdentityList"); + // first remove any deleted identities + for (const deletedUUID of deletedIdentityList) { + const deletedCookieStoreId = + await identityState.lookupCookieStoreId(deletedUUID); + if (deletedCookieStoreId){ + await browser.contextualIdentities.remove(deletedCookieStoreId); + } + } + const localIdentities = await browser.contextualIdentities.query({}); const syncIdentities = await sync.storageArea.getStoredObject("identities"); @@ -338,30 +372,6 @@ async function setAssignmentWithUUID (newUUID, assignedSite, urlKey) { throw new Error (`No cookieStoreId found for: ${newUUID}, ${urlKey}`); } -async function runSync() { - browser.storage.onChanged.removeListener( - sync.storageArea.onChangedListener); - removeContextualIdentityListeners(); - console.log("runSync"); - await identityState.storageArea.cleanup(); - await sync.storageArea.cleanup(); - if (await sync.storageArea.hasSyncStorage()){ - console.log("storage found, attempting to restore ..."); - await restore(); - return; - } - console.log("no sync storage, backing up..."); - await sync.storageArea.backup(); - return; -} - -async function restore() { - console.log("restore"); - await reconcileIdentitiesByUUID(); - await reconcileSiteAssignments(); - await sync.storageArea.backup(); -} - /* * Matches uuids in sync to uuids locally, and updates containers accordingly. * If there is no match, it creates the new container. @@ -371,9 +381,9 @@ async function reconcileIdentitiesByUUID() { const syncIdentities = await sync.storageArea.getStoredObject("identities"); const cookieStoreIDmap = await sync.storageArea.getStoredObject("cookieStoreIDmap"); + const deletedIdentityList = await sync.storageArea.getStoredArray("deletedIdentityList"); - // first remove any deleted identities for (const deletedUUID of deletedIdentityList) { const deletedCookieStoreId = diff --git a/src/js/background/test.js b/src/js/background/test.js index 97a6c53..1001075 100644 --- a/src/js/background/test.js +++ b/src/js/background/test.js @@ -1,24 +1,54 @@ browser.tests = { async runAll() { + await this.testIdentityStateCleanup(); await this.test1(); await this.test2(); }, + async testIdentityStateCleanup() { + await browser.tests.stopSyncListeners(); + console.log("Testing the cleanup of local storage"); + await this.removeAllContainers(); + const localData = { + "browserActionBadgesClicked": [ "6.1.1" ], + "containerTabsOpened": 7, + "identitiesState@@_firefox-default": { "hiddenTabs": [] }, + "onboarding-stage": 5, + "identitiesState@@_firefox-container-7": { "hiddenTabs": [] } + }; + await browser.storage.local.clear(); + await browser.storage.local.set(localData); + // async function assignIdentities () { + for (const containerInputSet of TEST_CONTAINERS) { + await browser.contextualIdentities.create(containerInputSet); + } + // } + // await assignIdentities(); + await identityState.storageArea.cleanup(); + const macConfigs = await browser.storage.local.get(); + const identities = []; + + for(const configKey of Object.keys(macConfigs)) { + if (configKey.includes("identitiesState@@_") && !configKey.includes("default")) { + identities.push(macConfigs[configKey]); + } + } + + console.assert(identities.length === 5, "There should be 5 identity entries"); + for (const identity of identities) { + console.assert(!!identity.macAddonUUID, `${identity.name} should have a uuid`); + } + console.log("Finished!"); + }, async test1() { await browser.tests.stopSyncListeners(); console.log("Testing new install with no sync"); // sync state on install: no sync data await browser.storage.sync.clear(); - await this.removeAllContainers(); await browser.storage.local.clear(); - const localData = { - "browserActionBadgesClicked": [ "6.1.1" ], - "containerTabsOpened": 7, - "identitiesState@@_firefox-default": { "hiddenTabs": [] }, - "onboarding-stage": 5 - }; - await browser.storage.local.set(localData); + await browser.storage.local.set(LOCAL_DATA); + await this.removeAllContainers(); for (const containerInputSet of TEST_CONTAINERS) { await browser.contextualIdentities.create(containerInputSet); } @@ -56,72 +86,13 @@ browser.tests = { async test2() { await browser.tests.stopSyncListeners(); - console.log("Testing sync differing"); + console.log("Testing sync differing from local"); - // sync state on install: no sync data - await browser.storage.sync.clear(); - const syncData = { - "identities": [ - { - "name": "Personal", - "icon": "fingerprint", - "iconUrl": "resource://usercontext-content/fingerprint.svg", - "color": "red", - "colorCode": "#37adff", - "cookieStoreId": "firefox-container-146" - }, - { - "name": "Oscar", - "icon": "dollar", - "iconUrl": "resource://usercontext-content/dollar.svg", - "color": "green", - "colorCode": "#51cd00", - "cookieStoreId": "firefox-container-147" - }, - { - "name": "Mozilla", - "icon": "pet", - "iconUrl": "resource://usercontext-content/briefcase.svg", - "color": "red", - "colorCode": "#ff613d", - "cookieStoreId": "firefox-container-148" - }, - { - "name": "Groceries, obviously", - "icon": "cart", - "iconUrl": "resource://usercontext-content/cart.svg", - "color": "pink", - "colorCode": "#ffcb00", - "cookieStoreId": "firefox-container-149" - }, - { - "name": "Facebook", - "icon": "fence", - "iconUrl": "resource://usercontext-content/fence.svg", - "color": "toolbar", - "colorCode": "#7c7c7d", - "cookieStoreId": "firefox-container-150" - } - ], - "cookieStoreIDmap": { - "firefox-container-146": "22ded543-5173-44a5-a47a-8813535945ca", - "firefox-container-147": "63e5212f-0858-418e-b5a3-09c2dea61fcd", - "firefox-container-148": "71335417-158e-4d74-a55b-e9e9081601ec", - "firefox-container-149": "59c4e5f7-fe3b-435a-ae60-1340db31a91b", - "firefox-container-150": "3dc916fb-8c0a-4538-9758-73ef819a45f7" - }, - "assignedSites": {} - }; - await browser.storage.sync.set(syncData); - await browser.storage.local.clear(); - const localData = { - "browserActionBadgesClicked": [ "6.1.1" ], - "containerTabsOpened": 7, - "identitiesState@@_firefox-default": { "hiddenTabs": [] }, - "onboarding-stage": 5 - }; await this.removeAllContainers(); - console.log("TEST_CONTAINERS.length", TEST_CONTAINERS.length); + await browser.storage.sync.clear(); + await browser.storage.sync.set(SYNC_DATA); + await browser.storage.local.clear(); + const localData = LOCAL_DATA; for (let i=0; i < TEST_CONTAINERS.length; i++) { //build identities const newIdentity = @@ -141,14 +112,16 @@ browser.tests = { console.log("local storage set: ", await browser.storage.local.get()); await sync.initSync(); - + const getSync = await browser.storage.sync.get(); const getAssignedSites = await assignManager.storageArea.getAssignedSites(); + const identities = await browser.contextualIdentities.query({}); + const localCookieStoreIDmap = await identityState.getCookieStoreIDuuidMap(); - console.log(getSync.cookieStoreIDmap); + console.assert( Object.keys(getSync.cookieStoreIDmap).length === 6, "cookieStoreIDmap should have 6 entries" @@ -225,7 +198,65 @@ const TEST_ASSIGNMENTS = [ "siteContainerMap@@_www.linkedin.com", "siteContainerMap@@_reddit.com" ]; +const LOCAL_DATA = { + "browserActionBadgesClicked": [ "6.1.1" ], + "containerTabsOpened": 7, + "identitiesState@@_firefox-default": { "hiddenTabs": [] }, + "onboarding-stage": 5 +}; +const SYNC_DATA = { + "identities": [ + { + "name": "Personal", + "icon": "fingerprint", + "iconUrl": "resource://usercontext-content/fingerprint.svg", + "color": "red", + "colorCode": "#37adff", + "cookieStoreId": "firefox-container-146" + }, + { + "name": "Oscar", + "icon": "dollar", + "iconUrl": "resource://usercontext-content/dollar.svg", + "color": "green", + "colorCode": "#51cd00", + "cookieStoreId": "firefox-container-147" + }, + { + "name": "Mozilla", + "icon": "pet", + "iconUrl": "resource://usercontext-content/briefcase.svg", + "color": "red", + "colorCode": "#ff613d", + "cookieStoreId": "firefox-container-148" + }, + { + "name": "Groceries, obviously", + "icon": "cart", + "iconUrl": "resource://usercontext-content/cart.svg", + "color": "pink", + "colorCode": "#ffcb00", + "cookieStoreId": "firefox-container-149" + }, + { + "name": "Facebook", + "icon": "fence", + "iconUrl": "resource://usercontext-content/fence.svg", + "color": "toolbar", + "colorCode": "#7c7c7d", + "cookieStoreId": "firefox-container-150" + } + ], + "cookieStoreIDmap": { + "firefox-container-146": "22ded543-5173-44a5-a47a-8813535945ca", + "firefox-container-147": "63e5212f-0858-418e-b5a3-09c2dea61fcd", + "firefox-container-148": "71335417-158e-4d74-a55b-e9e9081601ec", + "firefox-container-149": "59c4e5f7-fe3b-435a-ae60-1340db31a91b", + "firefox-container-150": "3dc916fb-8c0a-4538-9758-73ef819a45f7" + }, + "assignedSites": {} +}; browser.resetMAC2 = async function () { // for debugging and testing: remove all containers except the default 4 and the first one created browser.tests.stopSyncListeners(); diff --git a/test/features/sync.test.js b/test/features/sync.test.js index ddc87a0..72656fa 100644 --- a/test/features/sync.test.js +++ b/test/features/sync.test.js @@ -1,6 +1,6 @@ describe("Sync", () => { - it.only("should init sync on startup", async () => { + it("should init sync on startup", async () => { console.log("!!!a") const tab = await helper.browser.initializeWithTab(); console.log(await background.browser.storage.local.get());