"Fossies" - the Fresh Open Source Software Archive

Member "jitsi-meet-7319/react/features/base/redux/PersistenceRegistry.ts" (6 Jun 2023, 8060 Bytes) of package /linux/misc/jitsi-meet-7319.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // @ts-expect-error
    2 import Bourne from '@hapi/bourne';
    3 // eslint-disable-next-line lines-around-comment
    4 // @ts-expect-error
    5 import { jitsiLocalStorage } from '@jitsi/js-utils';
    6 import md5 from 'js-md5';
    7 
    8 import logger from './logger';
    9 
   10 declare let __DEV__: any;
   11 
   12 /**
   13  * Mixed type of the element (subtree) config. If it's a {@code boolean} (and is
   14  * {@code true}), we persist the entire subtree. If it's an {@code Object}, we
   15  * perist a filtered subtree based on the properties of the config object.
   16  */
   17 declare type ElementConfig = boolean | Object;
   18 
   19 /**
   20  * The type of the name-config pairs stored in {@code PersistenceRegistry}.
   21  */
   22 declare type PersistencyConfigMap = { [name: string]: ElementConfig; };
   23 
   24 /**
   25  * A registry to allow features to register their redux store subtree to be
   26  * persisted and also handles the persistency calls too.
   27  */
   28 class PersistenceRegistry {
   29     _checksum = '';
   30     _defaultStates: { [name: string ]: Object | undefined; } = {};
   31     _elements: PersistencyConfigMap = {};
   32 
   33     /**
   34      * Returns the persisted redux state. Takes the {@link #_elements} into
   35      * account as we may have persisted something in the past that we don't want
   36      * to retrieve anymore. The next {@link #persistState} will remove such
   37      * values.
   38      *
   39      * @returns {Object}
   40      */
   41     getPersistedState() {
   42         const filteredPersistedState: any = {};
   43 
   44         // localStorage key per feature
   45         for (const subtreeName of Object.keys(this._elements)) {
   46             // Assumes that the persisted value is stored under the same key as
   47             // the feature's redux state name.
   48             const persistedSubtree
   49                 = this._getPersistedSubtree(
   50                     subtreeName,
   51                     this._elements[subtreeName],
   52                     this._defaultStates[subtreeName]);
   53 
   54             if (persistedSubtree !== undefined) {
   55                 filteredPersistedState[subtreeName] = persistedSubtree;
   56             }
   57         }
   58 
   59         // Initialize the checksum.
   60         this._checksum = this._calculateChecksum(filteredPersistedState);
   61 
   62         if (typeof __DEV__ !== 'undefined' && __DEV__) {
   63             logger.info('redux state rehydrated as', filteredPersistedState);
   64         }
   65 
   66         return filteredPersistedState;
   67     }
   68 
   69     /**
   70      * Initiates a persist operation, but its execution will depend on the
   71      * current checksums (checks changes).
   72      *
   73      * @param {Object} state - The redux state.
   74      * @returns {void}
   75      */
   76     persistState(state: Object) {
   77         const filteredState = this._getFilteredState(state);
   78         const checksum = this._calculateChecksum(filteredState);
   79 
   80         if (checksum !== this._checksum) {
   81             for (const subtreeName of Object.keys(filteredState)) {
   82                 try {
   83                     jitsiLocalStorage.setItem(subtreeName, JSON.stringify(filteredState[subtreeName]));
   84                 } catch (error) {
   85                     logger.error('Error persisting redux subtree', subtreeName, error);
   86                 }
   87             }
   88             logger.info(`redux state persisted. ${this._checksum} -> ${checksum}`);
   89             this._checksum = checksum;
   90         }
   91     }
   92 
   93     /**
   94      * Registers a new subtree config to be used for the persistency.
   95      *
   96      * @param {string} name - The name of the subtree the config belongs to.
   97      * @param {ElementConfig} config - The config {@code Object}, or
   98      * {@code boolean} if the entire subtree needs to be persisted.
   99      * @param {Object} defaultState - The default state of the component. If
  100      * it's provided, the rehydrated state will be merged with it before it gets
  101      * pushed into Redux.
  102      * @returns {void}
  103      */
  104     register(
  105             name: string,
  106             config: ElementConfig = true,
  107             defaultState?: Object) {
  108         this._elements[name] = config;
  109         this._defaultStates[name] = defaultState;
  110     }
  111 
  112     /**
  113      * Calculates the checksum of a specific state.
  114      *
  115      * @param {Object} state - The redux state to calculate the checksum of.
  116      * @private
  117      * @returns {string} The checksum of the specified {@code state}.
  118      */
  119     _calculateChecksum(state: Object) {
  120         try {
  121             return md5.hex(JSON.stringify(state) || '');
  122         } catch (error) {
  123             logger.error('Error calculating checksum for state', error);
  124 
  125             return '';
  126         }
  127     }
  128 
  129     /**
  130      * Prepares a filtered state from the actual or the persisted redux state,
  131      * based on this registry.
  132      *
  133      * @param {Object} state - The actual or persisted redux state.
  134      * @private
  135      * @returns {Object}
  136      */
  137     _getFilteredState(state: any): any {
  138         const filteredState: any = {};
  139 
  140         for (const name of Object.keys(this._elements)) {
  141             if (state[name]) {
  142                 filteredState[name]
  143                     = this._getFilteredSubtree(
  144                         state[name],
  145                         this._elements[name]);
  146             }
  147         }
  148 
  149         return filteredState;
  150     }
  151 
  152     /**
  153      * Prepares a filtered subtree based on the config for persisting or for
  154      * retrieval.
  155      *
  156      * @param {Object} subtree - The redux state subtree.
  157      * @param {ElementConfig} subtreeConfig - The related config.
  158      * @private
  159      * @returns {Object}
  160      */
  161     _getFilteredSubtree(subtree: any, subtreeConfig: any) {
  162         let filteredSubtree: any;
  163 
  164         if (typeof subtreeConfig === 'object') {
  165             // Only a filtered subtree gets persisted as specified by
  166             // subtreeConfig.
  167             filteredSubtree = {};
  168             for (const persistedKey of Object.keys(subtree)) {
  169                 if (subtreeConfig[persistedKey]) {
  170                     filteredSubtree[persistedKey] = subtree[persistedKey];
  171                 }
  172             }
  173         } else if (subtreeConfig) {
  174             // Persist the entire subtree.
  175             filteredSubtree = subtree;
  176         }
  177 
  178         return filteredSubtree;
  179     }
  180 
  181     /**
  182      * Retrieves a persisted subtree from the storage.
  183      *
  184      * @param {string} subtreeName - The name of the subtree.
  185      * @param {Object} subtreeConfig - The config of the subtree from
  186      * {@link #_elements}.
  187      * @param {Object} subtreeDefaults - The defaults of the persisted subtree.
  188      * @private
  189      * @returns {Object}
  190      */
  191     _getPersistedSubtree(subtreeName: string, subtreeConfig: Object, subtreeDefaults?: Object) {
  192         let persistedSubtree = jitsiLocalStorage.getItem(subtreeName);
  193 
  194         if (persistedSubtree) {
  195             try {
  196                 persistedSubtree = Bourne.parse(persistedSubtree);
  197 
  198                 const filteredSubtree
  199                     = this._getFilteredSubtree(persistedSubtree, subtreeConfig);
  200 
  201                 if (filteredSubtree !== undefined) {
  202                     return this._mergeDefaults(
  203                         filteredSubtree, subtreeDefaults);
  204                 }
  205             } catch (error) {
  206                 logger.error(
  207                     'Error parsing persisted subtree',
  208                     subtreeName,
  209                     persistedSubtree,
  210                     error);
  211             }
  212         }
  213 
  214         return undefined;
  215     }
  216 
  217     /**
  218      * Merges the persisted subtree with its defaults before rehydrating the
  219      * values.
  220      *
  221      * @private
  222      * @param {Object} subtree - The Redux subtree.
  223      * @param {?Object} defaults - The defaults, if any.
  224      * @returns {Object}
  225      */
  226     _mergeDefaults(subtree: Object, defaults?: Object) {
  227         if (!defaults) {
  228             return subtree;
  229         }
  230 
  231         // If the subtree is an array, we don't need to merge it with the
  232         // defaults, because if it has a value, it will overwrite it, and if
  233         // it's undefined, it won't be even returned, and Redux will natively
  234         // use the default values instead.
  235         if (!Array.isArray(subtree)) {
  236             return {
  237                 ...defaults,
  238                 ...subtree
  239             };
  240         }
  241     }
  242 }
  243 
  244 export default new PersistenceRegistry();