Make web use the redux settings/profile
This commit is contained in:
committed by
Saúl Ibarra Corretgé
parent
ab7e572162
commit
959db3a665
@@ -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');
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user