2018-01-17 11:19:10 +00:00
|
|
|
// @flow
|
2018-07-11 09:42:43 +00:00
|
|
|
import { APP_WILL_MOUNT } from '../base/app';
|
2018-04-04 19:54:42 +00:00
|
|
|
import { getURLWithoutParamsNormalized } from '../base/connection';
|
2018-02-02 19:35:49 +00:00
|
|
|
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 {
|
2018-05-14 17:37:48 +00:00
|
|
|
_STORE_CURRENT_CONFERENCE,
|
2018-09-25 12:48:03 +00:00
|
|
|
_UPDATE_CONFERENCE_DURATION,
|
|
|
|
DELETE_RECENT_LIST_ENTRY
|
2018-01-17 11:19:10 +00:00
|
|
|
} from './actionTypes';
|
2018-08-01 20:37:15 +00:00
|
|
|
import { isRecentListEnabled } from './functions';
|
2018-02-02 18:13:33 +00:00
|
|
|
|
|
|
|
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
/**
|
|
|
|
* 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';
|
|
|
|
|
|
|
|
/**
|
2018-02-27 20:21:28 +00:00
|
|
|
* Sets up the persistence of the feature {@code recent-list}.
|
2018-01-17 11:19:10 +00:00
|
|
|
*/
|
2018-02-06 09:43:49 +00:00
|
|
|
PersistenceRegistry.register(STORE_NAME);
|
2018-01-17 11:19:10 +00:00
|
|
|
|
|
|
|
/**
|
2018-02-27 20:21:28 +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,
|
2018-02-06 09:43:49 +00:00
|
|
|
(state = _getLegacyRecentRoomList(), action) => {
|
2018-08-01 20:37:15 +00:00
|
|
|
if (isRecentListEnabled()) {
|
2018-08-01 16:41:54 +00:00
|
|
|
switch (action.type) {
|
|
|
|
case APP_WILL_MOUNT:
|
|
|
|
return _appWillMount(state);
|
2018-09-25 12:48:03 +00:00
|
|
|
case DELETE_RECENT_LIST_ENTRY:
|
|
|
|
return _deleteRecentListEntry(state, action.entryId);
|
2018-08-01 16:41:54 +00:00
|
|
|
case _STORE_CURRENT_CONFERENCE:
|
|
|
|
return _storeCurrentConference(state, action);
|
2018-02-02 18:13:33 +00:00
|
|
|
|
2018-08-01 16:41:54 +00:00
|
|
|
case _UPDATE_CONFERENCE_DURATION:
|
|
|
|
return _updateConferenceDuration(state, action);
|
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
2018-02-02 18:13:33 +00:00
|
|
|
}
|
2018-08-01 20:37:15 +00:00
|
|
|
|
|
|
|
return state;
|
2018-02-02 18:13:33 +00:00
|
|
|
});
|
|
|
|
|
2018-09-25 12:48:03 +00:00
|
|
|
/**
|
|
|
|
* Deletes a recent list entry based on the url and date of the item.
|
|
|
|
*
|
|
|
|
* @param {Array<Object>} state - The Redux state.
|
|
|
|
* @param {Object} entryId - The ID object of the entry.
|
|
|
|
* @returns {Array<Object>}
|
|
|
|
*/
|
|
|
|
function _deleteRecentListEntry(
|
|
|
|
state: Array<Object>, entryId: Object): Array<Object> {
|
|
|
|
return state.filter(entry =>
|
|
|
|
entry.conference !== entryId.url || entry.date !== entryId.date);
|
|
|
|
}
|
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
*/
|
2018-02-27 20:21:28 +00:00
|
|
|
function _getLegacyRecentRoomList(): Array<Object> {
|
2018-02-02 18:13:33 +00:00
|
|
|
try {
|
2018-02-27 20:21:28 +00:00
|
|
|
const str = window.localStorage.getItem(LEGACY_STORAGE_KEY);
|
2018-02-02 18:13:33 +00:00
|
|
|
|
2018-02-27 20:21:28 +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
|
|
|
|
|
|
|
/**
|
2018-02-27 20:21:28 +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.
|
2018-02-27 20:21:28 +00:00
|
|
|
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).
|
2018-02-27 20:21:28 +00:00
|
|
|
nextState.push({
|
2018-01-17 11:19:10 +00:00
|
|
|
conference,
|
2018-02-27 20:21:28 +00:00
|
|
|
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.
|
2018-02-27 20:21:28 +00:00
|
|
|
nextState.splice(0, nextState.length - MAX_LIST_SIZE);
|
2018-01-17 11:19:10 +00:00
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
return nextState;
|
2018-01-17 11:19:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the conference length when left.
|
|
|
|
*
|
2018-02-27 20:21:28 +00:00
|
|
|
* @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.
|
2018-02-27 20:21:28 +00:00
|
|
|
* @returns {Object} The next redux state of the feature {@code recent-list}.
|
2018-01-17 11:19:10 +00:00
|
|
|
*/
|
2018-02-27 20:21:28 +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
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
delete nextMostRecent.conferenceDuration; // legacy
|
2018-01-17 11:19:10 +00:00
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
// Shallow copy to avoid in-place modification.
|
|
|
|
const nextState = state.slice();
|
2018-01-17 11:19:10 +00:00
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
nextState[mostRecentIndex] = nextMostRecent;
|
2018-01-17 11:19:10 +00:00
|
|
|
|
2018-02-27 20:21:28 +00:00
|
|
|
return nextState;
|
2018-01-17 11:19:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
2018-02-27 20:21:28 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
2018-04-04 19:54:42 +00:00
|
|
|
const aHref = getURLWithoutParamsNormalized(new URL(a));
|
|
|
|
const bHref = getURLWithoutParamsNormalized(new URL(b));
|
|
|
|
|
|
|
|
return aHref === bHref;
|
2018-02-27 20:21:28 +00:00
|
|
|
}
|