Make web use the redux settings/profile

This commit is contained in:
zbettenbuk
2018-05-09 15:20:22 +02:00
committed by Saúl Ibarra Corretgé
parent ab7e572162
commit 959db3a665
29 changed files with 455 additions and 434 deletions
@@ -0,0 +1,22 @@
/**
* Create an action for when the settings are updated.
*
* {
* type: SETTINGS_UPDATED,
* settings: {
* audioOutputDeviceId: string,
* avatarID: string,
* avatarURL: string,
* cameraDeviceId: string,
* displayName: string,
* email: string,
* localFlipX: boolean,
* micDeviceId: string,
* serverURL: string,
* startAudioOnly: boolean,
* startWithAudioMuted: boolean,
* startWithVideoMuted: boolean
* }
* }
*/
export const SETTINGS_UPDATED = Symbol('SETTINGS_UPDATED');
+30
View File
@@ -0,0 +1,30 @@
import { SETTINGS_UPDATED } from './actionTypes';
/**
* Create an action for when the settings are updated.
*
* @param {Object} settings - The new (partial) settings properties.
* @returns {{
* type: SETTINGS_UPDATED,
* settings: {
* audioOutputDeviceId: string,
* avatarID: string,
* avatarURL: string,
* cameraDeviceId: string,
* displayName: string,
* email: string,
* localFlipX: boolean,
* micDeviceId: string,
* serverURL: string,
* startAudioOnly: boolean,
* startWithAudioMuted: boolean,
* startWithVideoMuted: boolean
* }
* }}
*/
export function updateSettings(settings) {
return {
type: SETTINGS_UPDATED,
settings
};
}
+85
View File
@@ -0,0 +1,85 @@
// @flow
import { parseURLParams } from '../config';
import { toState } from '../redux';
/**
* Returns the effective value of a configuration/preference/setting by applying
* a precedence among the values specified by JWT, URL, settings,
* and config.
*
* @param {Object|Function} stateful - The redux state object or
* {@code getState} function.
* @param {string} propertyName - The name of the
* configuration/preference/setting (property) to retrieve.
* @param {{
* config: boolean,
* jwt: boolean,
* settings: boolean,
* urlParams: boolean
* }} [sources] - A set/structure of {@code boolean} flags indicating the
* configuration/preference/setting sources to consider/retrieve values from.
* @returns {any}
*/
export function getPropertyValue(
stateful: Object | Function,
propertyName: string,
sources?: Object
) {
// Default values don't play nicely with partial objects and we want to make
// the function easy to use without exhaustively defining all flags:
sources = { // eslint-disable-line no-param-reassign
// Defaults:
config: true,
jwt: true,
settings: true,
urlParams: true,
...sources
};
// Precedence: jwt -> urlParams -> settings -> config.
const state = toState(stateful);
// jwt
if (sources.jwt) {
const value = state['features/base/jwt'][propertyName];
if (typeof value !== 'undefined') {
return value[propertyName];
}
}
// urlParams
if (sources.urlParams) {
const urlParams
= parseURLParams(state['features/base/connection'].locationURL);
const value = urlParams[`config.${propertyName}`];
if (typeof value !== 'undefined') {
return value;
}
}
// settings
if (sources.settings) {
const value = state['features/base/settings'][propertyName];
if (typeof value !== 'undefined') {
return value;
}
}
// config
if (sources.config) {
const value = state['features/base/config'][propertyName];
if (typeof value !== 'undefined') {
return value;
}
}
return undefined;
}
+5
View File
@@ -0,0 +1,5 @@
export * from './actions';
export * from './functions';
import './middleware';
import './reducer';
@@ -0,0 +1,67 @@
// @flow
import { setAudioOnly } from '../conference';
import { getLocalParticipant, participantUpdated } from '../participants';
import { MiddlewareRegistry, toState } from '../redux';
import { SETTINGS_UPDATED } from './actionTypes';
import { getSettings } from './functions';
/**
* The middleware of the feature base/settings. Distributes changes to the state
* of base/settings to the states of other features computed from the state of
* base/settings.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
const result = next(action);
switch (action.type) {
case SETTINGS_UPDATED:
_maybeSetAudioOnly(store, action);
_updateLocalParticipant(store);
}
return result;
});
/**
* Updates {@code startAudioOnly} flag if it's updated in the settings.
*
* @param {Store} store - The redux store.
* @param {Object} action - The redux action.
* @private
* @returns {void}
*/
function _maybeSetAudioOnly(
{ dispatch },
{ settings: { startAudioOnly } }) {
if (typeof startAudioOnly === 'boolean') {
dispatch(setAudioOnly(startAudioOnly));
}
}
/**
* Updates the local participant according to settings changes.
*
* @param {Store} store - The redux store.
* @private
* @returns {void}
*/
function _updateLocalParticipant(store) {
const state = toState(store);
const localParticipant = getLocalParticipant(state);
const settings = getSettings(state);
store.dispatch(participantUpdated({
// Identify that the participant to update i.e. the local participant:
id: localParticipant && localParticipant.id,
local: true,
// Specify the updates to be applied to the identified participant:
email: settings.email,
name: settings.displayName
}));
}
+157
View File
@@ -0,0 +1,157 @@
// @flow
import _ from 'lodash';
import { APP_WILL_MOUNT } from '../../app';
import JitsiMeetJS, { browser } from '../lib-jitsi-meet';
import { ReducerRegistry } from '../redux';
import { PersistenceRegistry } from '../storage';
import { assignIfDefined, randomHexString } from '../util';
import { SETTINGS_UPDATED } from './actionTypes';
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* The default/initial redux state of the feature {@code base/settings}.
*
* @type Object
*/
const DEFAULT_STATE = {
audioOutputDeviceId: undefined,
avatarID: undefined,
avatarURL: undefined,
cameraDeviceId: undefined,
displayName: undefined,
email: undefined,
localFlipX: true,
micDeviceId: undefined,
serverURL: undefined,
startAudioOnly: false,
startWithAudioMuted: false,
startWithVideoMuted: false
};
const STORE_NAME = 'features/base/settings';
/**
* Sets up the persistence of the feature {@code base/settings}.
*/
PersistenceRegistry.register(STORE_NAME);
ReducerRegistry.register(STORE_NAME, (state = DEFAULT_STATE, action) => {
switch (action.type) {
case APP_WILL_MOUNT:
return _initSettings(state);
case SETTINGS_UPDATED:
return {
...state,
...action.settings
};
}
return state;
});
/**
* Retrieves the legacy profile values regardless of it's being in pre or
* post-flattening format.
*
* FIXME: Let's remove this after a predefined time (e.g. by July 2018) to avoid
* garbage in the source.
*
* @private
* @returns {Object}
*/
function _getLegacyProfile() {
let persistedProfile
= window.localStorage.getItem('features/base/profile');
if (persistedProfile) {
try {
persistedProfile = JSON.parse(persistedProfile);
if (persistedProfile && typeof persistedProfile === 'object') {
const preFlattenedProfile = persistedProfile.profile;
return preFlattenedProfile || persistedProfile;
}
} catch (e) {
logger.warn('Error parsing persisted legacy profile', e);
}
}
return {};
}
/**
* Inits the settings object based on what information we have available.
* Info taken into consideration:
* - Old Settings.js style data
* - Things that we stored in profile earlier but belong here.
*
* @private
* @param {Object} featureState - The current state of the feature.
* @returns {Object}
*/
function _initSettings(featureState) {
let settings = featureState;
// Old Settings.js values
// FIXME: Let's remove this after a predefined time (e.g. by July 2018) to
// avoid garbage in the source.
const displayName = _.escape(window.localStorage.getItem('displayname'));
const email = _.escape(window.localStorage.getItem('email'));
let avatarID = _.escape(window.localStorage.getItem('avatarId'));
if (!avatarID) {
// if there is no avatar id, we generate a unique one and use it forever
avatarID = randomHexString(32);
}
settings = assignIfDefined({
avatarID,
displayName,
email
}, settings);
if (!browser.isReactNative()) {
// Browser only
const localFlipX
= JSON.parse(window.localStorage.getItem('localFlipX') || 'true');
const cameraDeviceId
= window.localStorage.getItem('cameraDeviceId') || '';
const micDeviceId = window.localStorage.getItem('micDeviceId') || '';
// Currently audio output device change is supported only in Chrome and
// default output always has 'default' device ID
const audioOutputDeviceId
= window.localStorage.getItem('audioOutputDeviceId') || 'default';
if (audioOutputDeviceId
!== JitsiMeetJS.mediaDevices.getAudioOutputDevice()) {
JitsiMeetJS.mediaDevices.setAudioOutputDevice(
audioOutputDeviceId
).catch(ex => {
logger.warn('Failed to set audio output device from local '
+ 'storage. Default audio output device will be used'
+ 'instead.', ex);
});
}
settings = assignIfDefined({
audioOutputDeviceId,
cameraDeviceId,
localFlipX,
micDeviceId
}, settings);
}
// Things we stored in profile earlier
const legacyProfile = _getLegacyProfile();
settings = assignIfDefined(legacyProfile, settings);
return settings;
}