|
|
|
@ -1,25 +1,23 @@ |
|
|
|
|
// @flow
|
|
|
|
|
|
|
|
|
|
import Logger from 'jitsi-meet-logger'; |
|
|
|
|
import md5 from 'js-md5'; |
|
|
|
|
|
|
|
|
|
const logger = Logger.getLogger(__filename); |
|
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* The name of the localStorage store where the app persists its values to. |
|
|
|
|
* The name of the {@code localStorage} store where the app persists its values. |
|
|
|
|
*/ |
|
|
|
|
const PERSISTED_STATE_NAME = 'jitsi-state'; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Mixed type of the element (subtree) config. If it's a boolean, |
|
|
|
|
* (and is true) we persist the entire subtree. If it's an object, |
|
|
|
|
* we perist a filtered subtree based on the properties in the |
|
|
|
|
* config object. |
|
|
|
|
* Mixed type of the element (subtree) config. If it's a {@code boolean} (and is |
|
|
|
|
* {@code true}), we persist the entire subtree. If it's an {@code Object}, we |
|
|
|
|
* perist a filtered subtree based on the properties of the config object. |
|
|
|
|
*/ |
|
|
|
|
declare type ElementConfig = Object | boolean; |
|
|
|
|
declare type ElementConfig = boolean | Object; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* The type of the name-config pairs stored in this reducer. |
|
|
|
|
* The type of the name-config pairs stored in {@code PersistenceRegistry}. |
|
|
|
|
*/ |
|
|
|
|
declare type PersistencyConfigMap = { [name: string]: ElementConfig }; |
|
|
|
|
|
|
|
|
@ -30,74 +28,64 @@ declare type PersistencyConfigMap = { [name: string]: ElementConfig }; |
|
|
|
|
class PersistenceRegistry { |
|
|
|
|
_checksum: string; |
|
|
|
|
|
|
|
|
|
_elements: PersistencyConfigMap; |
|
|
|
|
_elements: PersistencyConfigMap = {}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Initializes a new {@ code PersistenceRegistry} instance. |
|
|
|
|
*/ |
|
|
|
|
constructor() { |
|
|
|
|
this._elements = {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns the persisted redux state. This function takes the |
|
|
|
|
* {@link #_elements} into account as we may have persisted something in the |
|
|
|
|
* past that we don't want to retreive anymore. The next |
|
|
|
|
* {@link #persistState} will remove those values. |
|
|
|
|
* Returns the persisted redux state. Takes the {@link #_elements} into |
|
|
|
|
* account as we may have persisted something in the past that we don't want |
|
|
|
|
* to retreive anymore. The next {@link #persistState} will remove such |
|
|
|
|
* values. |
|
|
|
|
* |
|
|
|
|
* @returns {Object} |
|
|
|
|
*/ |
|
|
|
|
getPersistedState() { |
|
|
|
|
let filteredPersistedState = {}; |
|
|
|
|
let persistedState = window.localStorage.getItem(PERSISTED_STATE_NAME); |
|
|
|
|
|
|
|
|
|
if (persistedState) { |
|
|
|
|
// This is the legacy implementation,
|
|
|
|
|
// must be removed in a later version.
|
|
|
|
|
try { |
|
|
|
|
persistedState = JSON.parse(persistedState); |
|
|
|
|
} catch (error) { |
|
|
|
|
logger.error( |
|
|
|
|
'Error parsing persisted state', |
|
|
|
|
persistedState, |
|
|
|
|
error); |
|
|
|
|
persistedState = {}; |
|
|
|
|
// localStorage key per feature
|
|
|
|
|
for (const subtreeName of Object.keys(this._elements)) { |
|
|
|
|
// Assumes that the persisted value is stored under the same key as
|
|
|
|
|
// the feature's redux state name.
|
|
|
|
|
// TODO We'll need to introduce functions later that can control the
|
|
|
|
|
// persist key's name. Similar to control serialization and
|
|
|
|
|
// deserialization. But that should be a straightforward change.
|
|
|
|
|
const persistedSubtree |
|
|
|
|
= this._getPersistedSubtree( |
|
|
|
|
subtreeName, |
|
|
|
|
this._elements[subtreeName]); |
|
|
|
|
|
|
|
|
|
if (persistedSubtree !== undefined) { |
|
|
|
|
filteredPersistedState[subtreeName] = persistedSubtree; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
filteredPersistedState |
|
|
|
|
= this._getFilteredState(persistedState); |
|
|
|
|
|
|
|
|
|
// legacy values must be written to the new store format and
|
|
|
|
|
// old values to be deleted, so then it'll never be used again.
|
|
|
|
|
this.persistState(filteredPersistedState); |
|
|
|
|
window.localStorage.removeItem(PERSISTED_STATE_NAME); |
|
|
|
|
} else { |
|
|
|
|
// new, split-keys implementation
|
|
|
|
|
for (const subtreeName of Object.keys(this._elements)) { |
|
|
|
|
/* |
|
|
|
|
* this assumes that the persisted value is stored under the |
|
|
|
|
* same key as the feature's redux state name. |
|
|
|
|
* We'll need to introduce functions later that can control |
|
|
|
|
* the persist key's name. Similar to control serialization |
|
|
|
|
* and deserialization. |
|
|
|
|
* But that should be a straightforward change. |
|
|
|
|
*/ |
|
|
|
|
const persistedSubtree |
|
|
|
|
= this._getPersistedSubtree( |
|
|
|
|
subtreeName, |
|
|
|
|
this._elements[subtreeName] |
|
|
|
|
); |
|
|
|
|
// legacy
|
|
|
|
|
if (Object.keys(filteredPersistedState).length === 0) { |
|
|
|
|
const { localStorage } = window; |
|
|
|
|
let persistedState = localStorage.getItem(PERSISTED_STATE_NAME); |
|
|
|
|
|
|
|
|
|
if (persistedSubtree !== undefined) { |
|
|
|
|
filteredPersistedState[subtreeName] = persistedSubtree; |
|
|
|
|
if (persistedState) { |
|
|
|
|
try { |
|
|
|
|
persistedState = JSON.parse(persistedState); |
|
|
|
|
} catch (error) { |
|
|
|
|
logger.error( |
|
|
|
|
'Error parsing persisted state', |
|
|
|
|
persistedState, |
|
|
|
|
error); |
|
|
|
|
persistedState = {}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
filteredPersistedState = this._getFilteredState(persistedState); |
|
|
|
|
|
|
|
|
|
// Store into the new format and delete the old format so that
|
|
|
|
|
// it's not used again.
|
|
|
|
|
this.persistState(filteredPersistedState); |
|
|
|
|
localStorage.removeItem(PERSISTED_STATE_NAME); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// initialize checksum
|
|
|
|
|
// Initialize the checksum.
|
|
|
|
|
this._checksum = this._calculateChecksum(filteredPersistedState); |
|
|
|
|
|
|
|
|
|
this._checksum = this._calculateChecksum(filteredPersistedState); |
|
|
|
|
logger.info('redux state rehydrated as', filteredPersistedState); |
|
|
|
|
|
|
|
|
|
return filteredPersistedState; |
|
|
|
@ -112,26 +100,25 @@ class PersistenceRegistry { |
|
|
|
|
*/ |
|
|
|
|
persistState(state: Object) { |
|
|
|
|
const filteredState = this._getFilteredState(state); |
|
|
|
|
const newCheckSum = this._calculateChecksum(filteredState); |
|
|
|
|
const checksum = this._calculateChecksum(filteredState); |
|
|
|
|
|
|
|
|
|
if (newCheckSum !== this._checksum) { |
|
|
|
|
if (checksum !== this._checksum) { |
|
|
|
|
for (const subtreeName of Object.keys(filteredState)) { |
|
|
|
|
try { |
|
|
|
|
window.localStorage.setItem( |
|
|
|
|
subtreeName, |
|
|
|
|
JSON.stringify(filteredState[subtreeName])); |
|
|
|
|
} catch (error) { |
|
|
|
|
logger.error('Error persisting redux subtree', |
|
|
|
|
logger.error( |
|
|
|
|
'Error persisting redux subtree', |
|
|
|
|
subtreeName, |
|
|
|
|
filteredState[subtreeName], |
|
|
|
|
error |
|
|
|
|
); |
|
|
|
|
error); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
logger.info( |
|
|
|
|
`redux state persisted. ${this._checksum} -> ${ |
|
|
|
|
newCheckSum}`);
|
|
|
|
|
this._checksum = newCheckSum; |
|
|
|
|
`redux state persisted. ${this._checksum} -> ${checksum}`); |
|
|
|
|
this._checksum = checksum; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -139,8 +126,8 @@ class PersistenceRegistry { |
|
|
|
|
* Registers a new subtree config to be used for the persistency. |
|
|
|
|
* |
|
|
|
|
* @param {string} name - The name of the subtree the config belongs to. |
|
|
|
|
* @param {ElementConfig} config - The config object, or boolean |
|
|
|
|
* if the entire subtree needs to be persisted. |
|
|
|
|
* @param {ElementConfig} config - The config {@code Object}, or |
|
|
|
|
* {@code boolean} if the entire subtree needs to be persisted. |
|
|
|
|
* @returns {void} |
|
|
|
|
*/ |
|
|
|
|
register(name: string, config?: ElementConfig = true) { |
|
|
|
@ -148,64 +135,28 @@ class PersistenceRegistry { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Calculates the checksum of the current or the new values of the state. |
|
|
|
|
* Calculates the checksum of a specific state. |
|
|
|
|
* |
|
|
|
|
* @param {Object} state - The redux state to calculate the checksum of. |
|
|
|
|
* @private |
|
|
|
|
* @param {Object} filteredState - The filtered/persisted redux state. |
|
|
|
|
* @returns {string} |
|
|
|
|
* @returns {string} The checksum of the specified {@code state}. |
|
|
|
|
*/ |
|
|
|
|
_calculateChecksum(filteredState: Object) { |
|
|
|
|
_calculateChecksum(state: Object) { |
|
|
|
|
try { |
|
|
|
|
return md5.hex(JSON.stringify(filteredState) || ''); |
|
|
|
|
return md5.hex(JSON.stringify(state) || ''); |
|
|
|
|
} catch (error) { |
|
|
|
|
logger.error( |
|
|
|
|
'Error calculating checksum for state', |
|
|
|
|
filteredState, |
|
|
|
|
error); |
|
|
|
|
logger.error('Error calculating checksum for state', state, error); |
|
|
|
|
|
|
|
|
|
return ''; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Retreives a persisted subtree from the storage. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {string} subtreeName - The name of the subtree. |
|
|
|
|
* @param {Object} subtreeConfig - The config of the subtree |
|
|
|
|
* from this._elements. |
|
|
|
|
* @returns {Object} |
|
|
|
|
*/ |
|
|
|
|
_getPersistedSubtree(subtreeName, subtreeConfig) { |
|
|
|
|
let persistedSubtree = window.localStorage.getItem(subtreeName); |
|
|
|
|
|
|
|
|
|
if (persistedSubtree) { |
|
|
|
|
try { |
|
|
|
|
persistedSubtree = JSON.parse(persistedSubtree); |
|
|
|
|
const filteredSubtree |
|
|
|
|
= this._getFilteredSubtree(persistedSubtree, subtreeConfig); |
|
|
|
|
|
|
|
|
|
if (filteredSubtree !== undefined) { |
|
|
|
|
return filteredSubtree; |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
logger.error( |
|
|
|
|
'Error parsing persisted subtree', |
|
|
|
|
subtreeName, |
|
|
|
|
persistedSubtree, |
|
|
|
|
error); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Prepares a filtered state from the actual or the persisted redux state, |
|
|
|
|
* based on this registry. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {Object} state - The actual or persisted redux state. |
|
|
|
|
* @private |
|
|
|
|
* @returns {Object} |
|
|
|
|
*/ |
|
|
|
|
_getFilteredState(state: Object) { |
|
|
|
@ -213,9 +164,10 @@ class PersistenceRegistry { |
|
|
|
|
|
|
|
|
|
for (const name of Object.keys(this._elements)) { |
|
|
|
|
if (state[name]) { |
|
|
|
|
filteredState[name] = this._getFilteredSubtree( |
|
|
|
|
state[name], |
|
|
|
|
this._elements[name]); |
|
|
|
|
filteredState[name] |
|
|
|
|
= this._getFilteredSubtree( |
|
|
|
|
state[name], |
|
|
|
|
this._elements[name]); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -226,30 +178,64 @@ class PersistenceRegistry { |
|
|
|
|
* Prepares a filtered subtree based on the config for persisting or for |
|
|
|
|
* retrieval. |
|
|
|
|
* |
|
|
|
|
* @private |
|
|
|
|
* @param {Object} subtree - The redux state subtree. |
|
|
|
|
* @param {ElementConfig} subtreeConfig - The related config. |
|
|
|
|
* @private |
|
|
|
|
* @returns {Object} |
|
|
|
|
*/ |
|
|
|
|
_getFilteredSubtree(subtree, subtreeConfig) { |
|
|
|
|
let filteredSubtree; |
|
|
|
|
|
|
|
|
|
if (subtreeConfig === true) { |
|
|
|
|
// we persist the entire subtree
|
|
|
|
|
filteredSubtree = subtree; |
|
|
|
|
} else if (typeof subtreeConfig === 'object') { |
|
|
|
|
// only a filtered subtree gets persisted, based on the
|
|
|
|
|
// subtreeConfig object.
|
|
|
|
|
if (typeof subtreeConfig === 'object') { |
|
|
|
|
// Only a filtered subtree gets persisted as specified by
|
|
|
|
|
// subtreeConfig.
|
|
|
|
|
filteredSubtree = {}; |
|
|
|
|
for (const persistedKey of Object.keys(subtree)) { |
|
|
|
|
if (subtreeConfig[persistedKey]) { |
|
|
|
|
filteredSubtree[persistedKey] = subtree[persistedKey]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} else if (subtreeConfig) { |
|
|
|
|
// Persist the entire subtree.
|
|
|
|
|
filteredSubtree = subtree; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return filteredSubtree; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Retreives a persisted subtree from the storage. |
|
|
|
|
* |
|
|
|
|
* @param {string} subtreeName - The name of the subtree. |
|
|
|
|
* @param {Object} subtreeConfig - The config of the subtree from |
|
|
|
|
* {@link #_elements}. |
|
|
|
|
* @private |
|
|
|
|
* @returns {Object} |
|
|
|
|
*/ |
|
|
|
|
_getPersistedSubtree(subtreeName, subtreeConfig) { |
|
|
|
|
let persistedSubtree = window.localStorage.getItem(subtreeName); |
|
|
|
|
|
|
|
|
|
if (persistedSubtree) { |
|
|
|
|
try { |
|
|
|
|
persistedSubtree = JSON.parse(persistedSubtree); |
|
|
|
|
|
|
|
|
|
const filteredSubtree |
|
|
|
|
= this._getFilteredSubtree(persistedSubtree, subtreeConfig); |
|
|
|
|
|
|
|
|
|
if (filteredSubtree !== undefined) { |
|
|
|
|
return filteredSubtree; |
|
|
|
|
} |
|
|
|
|
} catch (error) { |
|
|
|
|
logger.error( |
|
|
|
|
'Error parsing persisted subtree', |
|
|
|
|
subtreeName, |
|
|
|
|
persistedSubtree, |
|
|
|
|
error); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return undefined; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export default new PersistenceRegistry(); |
|
|
|
|