You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PersistenceRegistry.js 8.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // @flow
  2. import Logger from 'jitsi-meet-logger';
  3. import md5 from 'js-md5';
  4. const logger = Logger.getLogger(__filename);
  5. /**
  6. * The name of the localStorage store where the app persists its values to.
  7. */
  8. const PERSISTED_STATE_NAME = 'jitsi-state';
  9. /**
  10. * Mixed type of the element (subtree) config. If it's a boolean,
  11. * (and is true) we persist the entire subtree. If it's an object,
  12. * we perist a filtered subtree based on the properties in the
  13. * config object.
  14. */
  15. declare type ElementConfig = Object | boolean;
  16. /**
  17. * The type of the name-config pairs stored in this reducer.
  18. */
  19. declare type PersistencyConfigMap = { [name: string]: ElementConfig };
  20. /**
  21. * A registry to allow features to register their redux store subtree to be
  22. * persisted and also handles the persistency calls too.
  23. */
  24. class PersistenceRegistry {
  25. _checksum: string;
  26. _elements: PersistencyConfigMap;
  27. /**
  28. * Initializes a new {@ code PersistenceRegistry} instance.
  29. */
  30. constructor() {
  31. this._elements = {};
  32. }
  33. /**
  34. * Returns the persisted redux state. This function takes the
  35. * {@link #_elements} into account as we may have persisted something in the
  36. * past that we don't want to retreive anymore. The next
  37. * {@link #persistState} will remove those values.
  38. *
  39. * @returns {Object}
  40. */
  41. getPersistedState() {
  42. let filteredPersistedState = {};
  43. let persistedState = window.localStorage.getItem(PERSISTED_STATE_NAME);
  44. if (persistedState) {
  45. // This is the legacy implementation,
  46. // must be removed in a later version.
  47. try {
  48. persistedState = JSON.parse(persistedState);
  49. } catch (error) {
  50. logger.error(
  51. 'Error parsing persisted state',
  52. persistedState,
  53. error);
  54. persistedState = {};
  55. }
  56. filteredPersistedState
  57. = this._getFilteredState(persistedState);
  58. // legacy values must be written to the new store format and
  59. // old values to be deleted, so then it'll never be used again.
  60. this.persistState(filteredPersistedState);
  61. window.localStorage.removeItem(PERSISTED_STATE_NAME);
  62. } else {
  63. // new, split-keys implementation
  64. for (const subtreeName of Object.keys(this._elements)) {
  65. /*
  66. * this assumes that the persisted value is stored under the
  67. * same key as the feature's redux state name.
  68. * We'll need to introduce functions later that can control
  69. * the persist key's name. Similar to control serialization
  70. * and deserialization.
  71. * But that should be a straightforward change.
  72. */
  73. const persistedSubtree
  74. = this._getPersistedSubtree(
  75. subtreeName,
  76. this._elements[subtreeName]
  77. );
  78. if (persistedSubtree !== undefined) {
  79. filteredPersistedState[subtreeName] = persistedSubtree;
  80. }
  81. }
  82. }
  83. // initialize checksum
  84. this._checksum = this._calculateChecksum(filteredPersistedState);
  85. this._checksum = this._calculateChecksum(filteredPersistedState);
  86. logger.info('redux state rehydrated as', filteredPersistedState);
  87. return filteredPersistedState;
  88. }
  89. /**
  90. * Initiates a persist operation, but its execution will depend on the
  91. * current checksums (checks changes).
  92. *
  93. * @param {Object} state - The redux state.
  94. * @returns {void}
  95. */
  96. persistState(state: Object) {
  97. const filteredState = this._getFilteredState(state);
  98. const newCheckSum = this._calculateChecksum(filteredState);
  99. if (newCheckSum !== this._checksum) {
  100. for (const subtreeName of Object.keys(filteredState)) {
  101. try {
  102. window.localStorage.setItem(
  103. subtreeName,
  104. JSON.stringify(filteredState[subtreeName]));
  105. } catch (error) {
  106. logger.error('Error persisting redux subtree',
  107. subtreeName,
  108. filteredState[subtreeName],
  109. error
  110. );
  111. }
  112. }
  113. logger.info(
  114. `redux state persisted. ${this._checksum} -> ${
  115. newCheckSum}`);
  116. this._checksum = newCheckSum;
  117. }
  118. }
  119. /**
  120. * Registers a new subtree config to be used for the persistency.
  121. *
  122. * @param {string} name - The name of the subtree the config belongs to.
  123. * @param {ElementConfig} config - The config object, or boolean
  124. * if the entire subtree needs to be persisted.
  125. * @returns {void}
  126. */
  127. register(name: string, config?: ElementConfig = true) {
  128. this._elements[name] = config;
  129. }
  130. /**
  131. * Calculates the checksum of the current or the new values of the state.
  132. *
  133. * @private
  134. * @param {Object} filteredState - The filtered/persisted redux state.
  135. * @returns {string}
  136. */
  137. _calculateChecksum(filteredState: Object) {
  138. try {
  139. return md5.hex(JSON.stringify(filteredState) || '');
  140. } catch (error) {
  141. logger.error(
  142. 'Error calculating checksum for state',
  143. filteredState,
  144. error);
  145. return '';
  146. }
  147. }
  148. /**
  149. * Retreives a persisted subtree from the storage.
  150. *
  151. * @private
  152. * @param {string} subtreeName - The name of the subtree.
  153. * @param {Object} subtreeConfig - The config of the subtree
  154. * from this._elements.
  155. * @returns {Object}
  156. */
  157. _getPersistedSubtree(subtreeName, subtreeConfig) {
  158. let persistedSubtree = window.localStorage.getItem(subtreeName);
  159. if (persistedSubtree) {
  160. try {
  161. persistedSubtree = JSON.parse(persistedSubtree);
  162. const filteredSubtree
  163. = this._getFilteredSubtree(persistedSubtree, subtreeConfig);
  164. if (filteredSubtree !== undefined) {
  165. return filteredSubtree;
  166. }
  167. } catch (error) {
  168. logger.error(
  169. 'Error parsing persisted subtree',
  170. subtreeName,
  171. persistedSubtree,
  172. error);
  173. }
  174. }
  175. return null;
  176. }
  177. /**
  178. * Prepares a filtered state from the actual or the persisted redux state,
  179. * based on this registry.
  180. *
  181. * @private
  182. * @param {Object} state - The actual or persisted redux state.
  183. * @returns {Object}
  184. */
  185. _getFilteredState(state: Object) {
  186. const filteredState = {};
  187. for (const name of Object.keys(this._elements)) {
  188. if (state[name]) {
  189. filteredState[name] = this._getFilteredSubtree(
  190. state[name],
  191. this._elements[name]);
  192. }
  193. }
  194. return filteredState;
  195. }
  196. /**
  197. * Prepares a filtered subtree based on the config for persisting or for
  198. * retrieval.
  199. *
  200. * @private
  201. * @param {Object} subtree - The redux state subtree.
  202. * @param {ElementConfig} subtreeConfig - The related config.
  203. * @returns {Object}
  204. */
  205. _getFilteredSubtree(subtree, subtreeConfig) {
  206. let filteredSubtree;
  207. if (subtreeConfig === true) {
  208. // we persist the entire subtree
  209. filteredSubtree = subtree;
  210. } else if (typeof subtreeConfig === 'object') {
  211. // only a filtered subtree gets persisted, based on the
  212. // subtreeConfig object.
  213. filteredSubtree = {};
  214. for (const persistedKey of Object.keys(subtree)) {
  215. if (subtreeConfig[persistedKey]) {
  216. filteredSubtree[persistedKey] = subtree[persistedKey];
  217. }
  218. }
  219. }
  220. return filteredSubtree;
  221. }
  222. }
  223. export default new PersistenceRegistry();