jiti-meet/react/features/recent-list/reducer.js

194 lines
5.5 KiB
JavaScript
Raw Normal View History

2018-01-17 11:19:10 +00:00
// @flow
import { APP_WILL_MOUNT } from '../app';
import { ReducerRegistry } from '../base/redux';
import { PersistenceRegistry } from '../base/storage';
2018-01-29 22:20:38 +00:00
2018-01-17 11:19:10 +00:00
import {
STORE_CURRENT_CONFERENCE,
UPDATE_CONFERENCE_DURATION
} from './actionTypes';
2018-02-02 18:13:33 +00:00
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* The default/initial redux state of the feature {@code recent-list}.
*
* @type {Array<Object>}
*/
const DEFAULT_STATE = [];
2018-02-02 18:13:33 +00:00
/**
* The name of the {@code window.localStorage} item where recent rooms are
* stored.
*
* @type {string}
*/
const LEGACY_STORAGE_KEY = 'recentURLs';
2018-01-17 11:19:10 +00:00
/**
2018-02-02 18:13:33 +00:00
* The max size of the list.
*
* @type {number}
*/
export const MAX_LIST_SIZE = 30;
/**
* The redux subtree of this feature.
2018-01-17 11:19:10 +00:00
*/
const STORE_NAME = 'features/recent-list';
/**
* Sets up the persistence of the feature {@code recent-list}.
2018-01-17 11:19:10 +00:00
*/
PersistenceRegistry.register(STORE_NAME);
2018-01-17 11:19:10 +00:00
/**
* Reduces redux actions for the purposes of the feature {@code recent-list}.
2018-02-02 18:13:33 +00:00
*/
ReducerRegistry.register(
STORE_NAME,
(state = _getLegacyRecentRoomList(), action) => {
2018-02-02 18:13:33 +00:00
switch (action.type) {
case APP_WILL_MOUNT:
return _appWillMount(state);
2018-02-02 18:13:33 +00:00
case STORE_CURRENT_CONFERENCE:
return _storeCurrentConference(state, action);
case UPDATE_CONFERENCE_DURATION:
return _updateConferenceDuration(state, action);
default:
return state;
}
});
/**
* Reduces the redux action {@link APP_WILL_MOUNT}.
*
* @param {Object} state - The redux state of the feature {@code recent-list}.
* @param {Action} action - The redux action {@code APP_WILL_MOUNT}.
* @returns {Array<Object>} The next redux state of the feature
* {@code recent-list}.
*/
function _appWillMount(state) {
// XXX APP_WILL_MOUNT is the earliest redux action of ours dispatched in the
// store. For the purposes of legacy support, make sure that the
// deserialized recent-list's state is in the format deemed current by the
// current app revision.
if (state && typeof state === 'object') {
if (Array.isArray(state)) {
return state;
}
// In an enterprise/internal build of Jitsi Meet for Android and iOS we
// had recent-list's state as an object with property list.
const { list } = state;
if (Array.isArray(list) && list.length) {
return list.slice();
}
}
// In the weird case that we have previously persisted/serialized null.
return DEFAULT_STATE;
}
2018-02-02 18:13:33 +00:00
/**
* Retrieves the recent room list that was stored using the legacy way.
*
* @returns {Array<Object>}
2018-01-17 11:19:10 +00:00
*/
function _getLegacyRecentRoomList(): Array<Object> {
2018-02-02 18:13:33 +00:00
try {
const str = window.localStorage.getItem(LEGACY_STORAGE_KEY);
2018-02-02 18:13:33 +00:00
if (str) {
return JSON.parse(str);
2018-02-02 18:13:33 +00:00
}
} catch (error) {
logger.warn('Failed to parse legacy recent-room list!');
2018-01-17 11:19:10 +00:00
}
2018-02-02 18:13:33 +00:00
return [];
}
2018-01-17 11:19:10 +00:00
/**
* Adds a new list entry to the redux store.
*
* @param {Object} state - The redux state of the feature {@code recent-list}.
* @param {Object} action - The redux action.
* @returns {Object}
*/
function _storeCurrentConference(state, { locationURL }) {
2018-01-17 11:19:10 +00:00
const conference = locationURL.href;
// If the current conference is already in the list, we remove it to re-add
// it to the top.
const nextState
= state.filter(e => !_urlStringEquals(e.conference, conference));
2018-01-17 11:19:10 +00:00
2018-02-02 18:13:33 +00:00
// The list is a reverse-sorted (i.e. the newer elements are at the end).
nextState.push({
2018-01-17 11:19:10 +00:00
conference,
date: Date.now(),
duration: 0 // We don't have the duration yet!
2018-01-17 11:19:10 +00:00
});
2018-02-02 18:13:33 +00:00
// Ensure the list doesn't exceed a/the maximum size.
nextState.splice(0, nextState.length - MAX_LIST_SIZE);
2018-01-17 11:19:10 +00:00
return nextState;
2018-01-17 11:19:10 +00:00
}
/**
* Updates the conference length when left.
*
* @param {Object} state - The redux state of the feature {@code recent-list}.
2018-01-17 11:19:10 +00:00
* @param {Object} action - The redux action.
* @returns {Object} The next redux state of the feature {@code recent-list}.
2018-01-17 11:19:10 +00:00
*/
function _updateConferenceDuration(state, { locationURL }) {
if (locationURL && locationURL.href && state.length) {
const mostRecentIndex = state.length - 1;
const mostRecent = state[mostRecentIndex];
if (_urlStringEquals(mostRecent.conference, locationURL.href)) {
// The last conference start was stored so we need to update the
// length.
const nextMostRecent = {
...mostRecent,
duration: Date.now() - mostRecent.date
};
2018-01-17 11:19:10 +00:00
delete nextMostRecent.conferenceDuration; // legacy
2018-01-17 11:19:10 +00:00
// Shallow copy to avoid in-place modification.
const nextState = state.slice();
2018-01-17 11:19:10 +00:00
nextState[mostRecentIndex] = nextMostRecent;
2018-01-17 11:19:10 +00:00
return nextState;
2018-01-17 11:19:10 +00:00
}
}
return state;
}
/**
* Determines whether two specific URL {@code strings} are equal in the sense
* that they identify one and the same conference resource (irrespective of
* time) for the purposes of the feature {@code recent-list}.
*
* @param {string} a - The URL {@code string} to test for equality to {@code b}.
* @param {string} b - The URL {@code string} to test for equality to {@code a}.
* @returns {boolean}
*/
function _urlStringEquals(a: string, b: string) {
// FIXME Case-sensitive comparison is wrong because the room name at least
// is case insensitive on the server and elsewhere (where it matters) in the
// client. I don't think domain names are case-sensitive either.
return a === b;
}