-
Notifications
You must be signed in to change notification settings - Fork 94
Fix issue #15321: The app crashes when the user is logged into multiple tabs and logs out of one of the tabs #237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import _ from 'underscore'; | ||
|
|
||
| function extendPrototype(localforage) { | ||
| const newLocalforage = localforage; | ||
| newLocalforage.removeItems = keys => new Promise((resolve) => { | ||
| _.each(keys, (key) => { | ||
| delete newLocalforage.storageMap[key]; | ||
| }); | ||
| resolve(); | ||
| }); | ||
| } | ||
|
|
||
| // eslint-disable-next-line import/prefer-default-export | ||
| export {extendPrototype}; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1047,55 +1047,46 @@ function initializeWithDefaultKeyStates() { | |
| function clear(keysToPreserve = []) { | ||
| return getAllKeys() | ||
| .then((keys) => { | ||
| const keyValuesToReset = []; | ||
| const defaultKeys = _.keys(defaultKeyStates); | ||
|
|
||
| // Get all the values for the keys that need to be preserved. These key/value pairs will be set | ||
| // in Onyx after the database is cleared(). | ||
| const keyValuesToPreserve = _.map(keysToPreserve, key => [key, cache.getValue(key)]); | ||
| const keysToBeClearedFromStorage = []; | ||
| const keyValuesToResetAsCollection = {}; | ||
| const keyValuesToResetIndividually = {}; | ||
|
|
||
| // The only keys that should not be cleared are: | ||
| // 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline | ||
| // status, or activeClients need to remain in Onyx even when signed out) | ||
| // 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them | ||
| // to null would cause unknown behavior) | ||
| const keysToClear = _.difference(keys, keysToPreserve, defaultKeys); | ||
| keyValuesToReset.push(..._.map(keysToClear, key => [key, null])); | ||
|
|
||
| // Remove any keysToPreserve from the defaultKeyStates because if they are passed in it has been explicitly | ||
| // called out to preserve those values instead of resetting them back | ||
| // to the default. | ||
| const defaultKeyValuePairs = _.pairs(_.omit(defaultKeyStates, ...keysToPreserve)); | ||
|
|
||
| // Add the default key value pairs to the keyValuesToReset so that they get set back to their default values | ||
| // when we clear Onyx | ||
| keyValuesToReset.push(...defaultKeyValuePairs); | ||
|
|
||
| // We now have all the key/values that need to be reset, but we're not done yet! | ||
| // There will be two groups of key/values and they each need to be updated a little bit differently. | ||
| // Collection keys need to be notified differently than non collection keys | ||
| const keyValuesToResetAsCollection = {}; | ||
| const keyValuesToResetIndividually = {}; | ||
|
|
||
| // Make sure that we also reset the cache values before clearing the values from storage. | ||
| // We do this before clearing Storage so that any call to clear() followed by merge() on a key with a | ||
| // default state results in the merged value getting saved, since the update from the merge() call would | ||
| // happen on the tick after the update from this clear() | ||
| _.each(keyValuesToReset, (keyValue) => { | ||
| const key = keyValue[0]; | ||
| const value = keyValue[1]; | ||
| cache.set(key, value); | ||
|
|
||
| const collectionKey = key.substring(0, key.indexOf('_') + 1); | ||
| if (collectionKey) { | ||
| if (!keyValuesToResetAsCollection[collectionKey]) { | ||
| keyValuesToResetAsCollection[collectionKey] = {}; | ||
| _.each(keys, (key) => { | ||
| const isKeyToPreserve = _.contains(keysToPreserve, key); | ||
| const isDefaultKey = _.has(defaultKeyStates, key); | ||
|
|
||
| // If the key is being removed or reset to default: | ||
| // 1. Update it in the cache | ||
| // 2. Figure out whether it is a collection key or not, | ||
| // since collection key subscribers need to be updated differently | ||
| if (!isKeyToPreserve) { | ||
| const oldValue = cache.getValue(key); | ||
| const newValue = _.get(defaultKeyStates, key, null); | ||
| if (newValue !== oldValue) { | ||
| cache.set(key, newValue); | ||
| const collectionKey = key.substring(0, key.indexOf('_') + 1); | ||
| if (collectionKey) { | ||
| if (!keyValuesToResetAsCollection[collectionKey]) { | ||
| keyValuesToResetAsCollection[collectionKey] = {}; | ||
| } | ||
| keyValuesToResetAsCollection[collectionKey][key] = newValue; | ||
| } else { | ||
| keyValuesToResetIndividually[key] = newValue; | ||
| } | ||
| } | ||
| keyValuesToResetAsCollection[collectionKey][key] = value; | ||
| } | ||
|
|
||
| if (isKeyToPreserve || isDefaultKey) { | ||
| return; | ||
| } | ||
|
|
||
| keyValuesToResetIndividually[key] = value; | ||
| // If it isn't preserved and doesn't have a default, we'll remove it | ||
| keysToBeClearedFromStorage.push(key); | ||
| }); | ||
|
|
||
| // Notify the subscribers for each key/value group so they can receive the new values | ||
|
|
@@ -1106,11 +1097,10 @@ function clear(keysToPreserve = []) { | |
| notifyCollectionSubscribersOnNextTick(key, value); | ||
| }); | ||
|
|
||
| // Call clear() and make sure that the default key/values and the key/values from the parameter | ||
| // are preserved in storage. This makes sure to always leave storage in a state that contains | ||
| // all the default values and any additional values that we want to remain after the database is cleared. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's keep a comment here to explain what we're doing and why
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @madmax330 Thanks for pointing that out. I have updated, please help to check again
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updated it to only say what and not why... |
||
| return Storage.clear() | ||
| .then(() => Storage.multiSet([...defaultKeyValuePairs, ...keyValuesToPreserve])); | ||
| const defaultKeyValuePairs = _.pairs(_.omit(defaultKeyStates, keysToPreserve)); | ||
|
|
||
| // Remove only the items that we want cleared from storage, and reset others to default | ||
| return Storage.removeItems(keysToBeClearedFromStorage).then(() => Storage.multiSet(defaultKeyValuePairs)); | ||
| }); | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,13 @@ const provider = { | |
| */ | ||
| removeItem: AsyncStorage.removeItem, | ||
|
|
||
| /** | ||
| * Remove given keys and their values from storage | ||
| * @param {Array} keys | ||
| * @returns {Promise} | ||
| */ | ||
| removeItems: keys => Promise.all(_.map(keys, key => AsyncStorage.removeItem(key))), | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is required because we're using |
||
|
|
||
| /** | ||
| * Clear everything from storage | ||
| * @returns {Promise<void>} | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by this. Does this actually delete the stuff from IndexedDB? It looks like it would not...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhh nvm it's a mock...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well this is a mock. This code has changed a bit since this PR was created, but the mock isn't actually writing anything to IndexedDB.
storageMapInternalis just an object so I thinkdeletedoes work here?