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.

reducer.js 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. // @flow
  2. import Bourne from '@hapi/bourne';
  3. import { jitsiLocalStorage } from '@jitsi/js-utils';
  4. import { APP_WILL_MOUNT } from '../base/app';
  5. import { getURLWithoutParamsNormalized } from '../base/connection';
  6. import { PersistenceRegistry, ReducerRegistry } from '../base/redux';
  7. import {
  8. _STORE_CURRENT_CONFERENCE,
  9. _UPDATE_CONFERENCE_DURATION,
  10. DELETE_RECENT_LIST_ENTRY
  11. } from './actionTypes';
  12. import { isRecentListEnabled } from './functions';
  13. import logger from './logger';
  14. /**
  15. * The default/initial redux state of the feature {@code recent-list}.
  16. *
  17. * @type {Array<Object>}
  18. */
  19. const DEFAULT_STATE = [];
  20. /**
  21. * The name of the {@code window.localStorage} item where recent rooms are
  22. * stored.
  23. *
  24. * @type {string}
  25. */
  26. const LEGACY_STORAGE_KEY = 'recentURLs';
  27. /**
  28. * The max size of the list.
  29. *
  30. * @type {number}
  31. */
  32. export const MAX_LIST_SIZE = 30;
  33. /**
  34. * The redux subtree of this feature.
  35. */
  36. const STORE_NAME = 'features/recent-list';
  37. /**
  38. * Sets up the persistence of the feature {@code recent-list}.
  39. */
  40. PersistenceRegistry.register(STORE_NAME);
  41. /**
  42. * Reduces redux actions for the purposes of the feature {@code recent-list}.
  43. */
  44. ReducerRegistry.register(
  45. STORE_NAME,
  46. (state = _getLegacyRecentRoomList(), action) => {
  47. if (isRecentListEnabled()) {
  48. switch (action.type) {
  49. case APP_WILL_MOUNT:
  50. return _appWillMount(state);
  51. case DELETE_RECENT_LIST_ENTRY:
  52. return _deleteRecentListEntry(state, action.entryId);
  53. case _STORE_CURRENT_CONFERENCE:
  54. return _storeCurrentConference(state, action);
  55. case _UPDATE_CONFERENCE_DURATION:
  56. return _updateConferenceDuration(state, action);
  57. default:
  58. return state;
  59. }
  60. }
  61. return state;
  62. });
  63. /**
  64. * Deletes a recent list entry based on the url and date of the item.
  65. *
  66. * @param {Array<Object>} state - The Redux state.
  67. * @param {Object} entryId - The ID object of the entry.
  68. * @returns {Array<Object>}
  69. */
  70. function _deleteRecentListEntry(
  71. state: Array<Object>, entryId: Object): Array<Object> {
  72. return state.filter(entry =>
  73. entry.conference !== entryId.url || entry.date !== entryId.date);
  74. }
  75. /**
  76. * Reduces the redux action {@link APP_WILL_MOUNT}.
  77. *
  78. * @param {Object} state - The redux state of the feature {@code recent-list}.
  79. * @param {Action} action - The redux action {@code APP_WILL_MOUNT}.
  80. * @returns {Array<Object>} The next redux state of the feature
  81. * {@code recent-list}.
  82. */
  83. function _appWillMount(state) {
  84. // XXX APP_WILL_MOUNT is the earliest redux action of ours dispatched in the
  85. // store. For the purposes of legacy support, make sure that the
  86. // deserialized recent-list's state is in the format deemed current by the
  87. // current app revision.
  88. if (state && typeof state === 'object') {
  89. if (Array.isArray(state)) {
  90. return state;
  91. }
  92. // In an enterprise/internal build of Jitsi Meet for Android and iOS we
  93. // had recent-list's state as an object with property list.
  94. const { list } = state;
  95. if (Array.isArray(list) && list.length) {
  96. return list.slice();
  97. }
  98. }
  99. // In the weird case that we have previously persisted/serialized null.
  100. return DEFAULT_STATE;
  101. }
  102. /**
  103. * Retrieves the recent room list that was stored using the legacy way.
  104. *
  105. * @returns {Array<Object>}
  106. */
  107. function _getLegacyRecentRoomList(): Array<Object> {
  108. const str = jitsiLocalStorage.getItem(LEGACY_STORAGE_KEY);
  109. if (str) {
  110. try {
  111. return Bourne.parse(str);
  112. } catch (error) {
  113. logger.warn('Failed to parse legacy recent-room list!');
  114. }
  115. }
  116. return [];
  117. }
  118. /**
  119. * Adds a new list entry to the redux store.
  120. *
  121. * @param {Object} state - The redux state of the feature {@code recent-list}.
  122. * @param {Object} action - The redux action.
  123. * @returns {Object}
  124. */
  125. function _storeCurrentConference(state, { locationURL }) {
  126. const conference = locationURL.href;
  127. // If the current conference is already in the list, we remove it to re-add
  128. // it to the top.
  129. const nextState
  130. = state.filter(e => !_urlStringEquals(e.conference, conference));
  131. // The list is a reverse-sorted (i.e. the newer elements are at the end).
  132. nextState.push({
  133. conference,
  134. date: Date.now(),
  135. duration: 0 // We don't have the duration yet!
  136. });
  137. // Ensure the list doesn't exceed a/the maximum size.
  138. nextState.splice(0, nextState.length - MAX_LIST_SIZE);
  139. return nextState;
  140. }
  141. /**
  142. * Updates the conference length when left.
  143. *
  144. * @param {Object} state - The redux state of the feature {@code recent-list}.
  145. * @param {Object} action - The redux action.
  146. * @returns {Object} The next redux state of the feature {@code recent-list}.
  147. */
  148. function _updateConferenceDuration(state, { locationURL }) {
  149. if (locationURL && locationURL.href && state.length) {
  150. const mostRecentIndex = state.length - 1;
  151. const mostRecent = state[mostRecentIndex];
  152. if (_urlStringEquals(mostRecent.conference, locationURL.href)) {
  153. // The last conference start was stored so we need to update the
  154. // length.
  155. const nextMostRecent = {
  156. ...mostRecent,
  157. duration: Date.now() - mostRecent.date
  158. };
  159. delete nextMostRecent.conferenceDuration; // legacy
  160. // Shallow copy to avoid in-place modification.
  161. const nextState = state.slice();
  162. nextState[mostRecentIndex] = nextMostRecent;
  163. return nextState;
  164. }
  165. }
  166. return state;
  167. }
  168. /**
  169. * Determines whether two specific URL {@code strings} are equal in the sense
  170. * that they identify one and the same conference resource (irrespective of
  171. * time) for the purposes of the feature {@code recent-list}.
  172. *
  173. * @param {string} a - The URL {@code string} to test for equality to {@code b}.
  174. * @param {string} b - The URL {@code string} to test for equality to {@code a}.
  175. * @returns {boolean}
  176. */
  177. function _urlStringEquals(a: string, b: string) {
  178. const aHref = getURLWithoutParamsNormalized(new URL(a));
  179. const bHref = getURLWithoutParamsNormalized(new URL(b));
  180. return aHref === bHref;
  181. }