2017-10-13 19:31:05 +00:00
|
|
|
// @flow
|
|
|
|
|
2017-04-21 10:00:50 +00:00
|
|
|
import jwtDecode from 'jwt-decode';
|
|
|
|
|
2017-06-05 18:19:25 +00:00
|
|
|
import {
|
|
|
|
CONFERENCE_FAILED,
|
|
|
|
CONFERENCE_LEFT,
|
|
|
|
CONFERENCE_WILL_LEAVE,
|
|
|
|
SET_ROOM
|
2017-10-05 22:54:13 +00:00
|
|
|
} from '../conference';
|
|
|
|
import { SET_CONFIG } from '../config';
|
|
|
|
import { SET_LOCATION_URL } from '../connection';
|
|
|
|
import { LIB_INIT_ERROR } from '../lib-jitsi-meet';
|
2017-06-27 22:56:55 +00:00
|
|
|
import {
|
|
|
|
getLocalParticipant,
|
|
|
|
getParticipantCount,
|
2017-10-06 17:52:23 +00:00
|
|
|
PARTICIPANT_JOINED,
|
|
|
|
participantUpdated
|
2017-10-05 22:54:13 +00:00
|
|
|
} from '../participants';
|
|
|
|
import { MiddlewareRegistry } from '../redux';
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-11-23 16:26:47 +00:00
|
|
|
import { setCalleeInfoVisible, setJWT } from './actions';
|
2017-04-21 10:00:50 +00:00
|
|
|
import { SET_JWT } from './actionTypes';
|
2017-05-26 22:11:33 +00:00
|
|
|
import { parseJWTFromURLParams } from './functions';
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
declare var APP: Object;
|
|
|
|
|
2017-04-21 10:00:50 +00:00
|
|
|
/**
|
|
|
|
* Middleware to parse token data upon setting a new room URL.
|
|
|
|
*
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Store} store - The redux store.
|
2017-04-21 10:00:50 +00:00
|
|
|
* @private
|
|
|
|
* @returns {Function}
|
|
|
|
*/
|
|
|
|
MiddlewareRegistry.register(store => next => action => {
|
|
|
|
switch (action.type) {
|
2017-06-05 18:19:25 +00:00
|
|
|
case CONFERENCE_FAILED:
|
|
|
|
case CONFERENCE_LEFT:
|
|
|
|
case CONFERENCE_WILL_LEAVE:
|
|
|
|
case LIB_INIT_ERROR:
|
|
|
|
case PARTICIPANT_JOINED:
|
|
|
|
case SET_ROOM:
|
2017-11-23 16:26:47 +00:00
|
|
|
return _maybeSetCalleeInfoVisible(store, next, action);
|
2017-06-05 18:19:25 +00:00
|
|
|
|
2017-04-21 10:00:50 +00:00
|
|
|
case SET_CONFIG:
|
2017-05-09 05:15:43 +00:00
|
|
|
case SET_LOCATION_URL:
|
2017-04-21 10:00:50 +00:00
|
|
|
// XXX The JSON Web Token (JWT) is not the only piece of state that we
|
|
|
|
// have decided to store in the feature jwt, there is isGuest as well
|
|
|
|
// which depends on the states of the features base/config and jwt. So
|
2017-05-09 05:15:43 +00:00
|
|
|
// the JSON Web Token comes from the conference/room's URL and isGuest
|
|
|
|
// needs a recalculation upon SET_CONFIG as well.
|
|
|
|
return _setConfigOrLocationURL(store, next, action);
|
2017-04-21 10:00:50 +00:00
|
|
|
|
|
|
|
case SET_JWT:
|
|
|
|
return _setJWT(store, next, action);
|
|
|
|
}
|
|
|
|
|
|
|
|
return next(action);
|
|
|
|
});
|
|
|
|
|
2017-06-05 18:19:25 +00:00
|
|
|
/**
|
|
|
|
* Notifies the feature jwt that a specific {@code action} is being dispatched
|
|
|
|
* within a specific redux {@code store} which may have an effect on the
|
2017-11-23 16:26:47 +00:00
|
|
|
* visiblity of (the) {@code CalleeInfo}.
|
2017-06-05 18:19:25 +00:00
|
|
|
*
|
|
|
|
* @param {Store} store - The redux store in which the specified {@code action}
|
|
|
|
* is being dispatched.
|
|
|
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
|
|
|
* specified {@code action} to the specified {@code store}.
|
|
|
|
* @param {Action} action - The redux action which is being dispatched in the
|
|
|
|
* specified {@code store}.
|
|
|
|
* @private
|
|
|
|
* @returns {Object} The new state that is the result of the reduction of the
|
|
|
|
* specified {@code action}.
|
|
|
|
*/
|
2017-11-23 16:26:47 +00:00
|
|
|
function _maybeSetCalleeInfoVisible({ dispatch, getState }, next, action) {
|
2017-06-05 18:19:25 +00:00
|
|
|
const result = next(action);
|
|
|
|
|
|
|
|
const state = getState();
|
2018-03-05 01:27:15 +00:00
|
|
|
const stateFeaturesBaseJWT = state['features/base/jwt'];
|
2017-11-23 16:26:47 +00:00
|
|
|
let calleeInfoVisible;
|
2017-06-05 18:19:25 +00:00
|
|
|
|
2018-03-05 01:27:15 +00:00
|
|
|
if (stateFeaturesBaseJWT.callee) {
|
2017-06-05 18:19:25 +00:00
|
|
|
const { conference, leaving, room } = state['features/base/conference'];
|
|
|
|
|
2017-11-23 16:26:47 +00:00
|
|
|
// XXX The CalleeInfo is to be displayed/visible as soon as possible
|
2017-10-13 19:31:05 +00:00
|
|
|
// including even before the conference is joined.
|
2017-06-05 18:19:25 +00:00
|
|
|
if (room && (!conference || conference !== leaving)) {
|
|
|
|
switch (action.type) {
|
|
|
|
case CONFERENCE_FAILED:
|
|
|
|
case CONFERENCE_LEFT:
|
|
|
|
case CONFERENCE_WILL_LEAVE:
|
|
|
|
case LIB_INIT_ERROR:
|
2017-11-23 16:26:47 +00:00
|
|
|
// Because the CalleeInfo is to be displayed/visible as soon as
|
2017-06-05 18:19:25 +00:00
|
|
|
// possible even before the connection is established and/or the
|
|
|
|
// conference is joined, it is very complicated to figure out
|
|
|
|
// based on the current state alone. In order to reduce the
|
|
|
|
// risks of displaying the CallOverly at inappropirate times, do
|
|
|
|
// not even attempt to figure out based on the current state.
|
|
|
|
// The (redux) actions listed above are also the equivalents of
|
|
|
|
// the execution ponints at which APP.UI.hideRingOverlay() used
|
|
|
|
// to be invoked.
|
|
|
|
break;
|
|
|
|
|
|
|
|
default: {
|
2017-11-23 16:26:47 +00:00
|
|
|
// The CalleeInfo is to no longer be displayed/visible as soon
|
2017-06-05 18:19:25 +00:00
|
|
|
// as another participant joins.
|
2017-11-23 16:26:47 +00:00
|
|
|
calleeInfoVisible
|
2017-10-13 19:31:05 +00:00
|
|
|
= getParticipantCount(state) === 1
|
|
|
|
&& Boolean(getLocalParticipant(state));
|
2017-06-05 18:19:25 +00:00
|
|
|
|
|
|
|
// However, the CallDialog is not to be displayed/visible again
|
|
|
|
// after all remote participants leave.
|
2017-11-23 16:26:47 +00:00
|
|
|
if (calleeInfoVisible
|
2018-03-05 01:27:15 +00:00
|
|
|
&& stateFeaturesBaseJWT.calleeInfoVisible === false) {
|
2017-11-23 16:26:47 +00:00
|
|
|
calleeInfoVisible = false;
|
2017-06-05 18:19:25 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-23 16:26:47 +00:00
|
|
|
dispatch(setCalleeInfoVisible(calleeInfoVisible));
|
2017-06-05 18:19:25 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-10-06 17:52:23 +00:00
|
|
|
/**
|
2017-10-13 19:31:05 +00:00
|
|
|
* Overwrites the properties {@code avatarURL}, {@code email}, and {@code name}
|
|
|
|
* of the local participant stored in the redux state base/participants.
|
2017-10-06 17:52:23 +00:00
|
|
|
*
|
|
|
|
* @param {Store} store - The redux store.
|
2017-10-13 19:31:05 +00:00
|
|
|
* @param {Object} localParticipant - The {@code Participant} structure to
|
|
|
|
* overwrite the local participant stored in the redux store base/participants
|
|
|
|
* with.
|
2017-10-06 17:52:23 +00:00
|
|
|
* @private
|
2017-10-13 19:31:05 +00:00
|
|
|
* @returns {void}
|
2017-10-06 17:52:23 +00:00
|
|
|
*/
|
2017-10-13 19:31:05 +00:00
|
|
|
function _overwriteLocalParticipant(
|
|
|
|
{ dispatch, getState },
|
2018-06-15 18:10:22 +00:00
|
|
|
{ avatarURL, email, name, features }) {
|
2017-10-13 19:31:05 +00:00
|
|
|
let localParticipant;
|
2017-10-06 17:52:23 +00:00
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
if ((avatarURL || email || name)
|
|
|
|
&& (localParticipant = getLocalParticipant(getState))) {
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
const newProperties: Object = {
|
|
|
|
id: localParticipant.id,
|
|
|
|
local: true
|
|
|
|
};
|
2017-10-06 17:52:23 +00:00
|
|
|
|
|
|
|
if (avatarURL) {
|
|
|
|
newProperties.avatarURL = avatarURL;
|
|
|
|
}
|
|
|
|
if (email) {
|
|
|
|
newProperties.email = email;
|
|
|
|
}
|
|
|
|
if (name) {
|
|
|
|
newProperties.name = name;
|
|
|
|
}
|
2018-06-15 18:10:22 +00:00
|
|
|
if (features) {
|
|
|
|
newProperties.features = features;
|
|
|
|
}
|
2017-10-06 17:52:23 +00:00
|
|
|
dispatch(participantUpdated(newProperties));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-21 10:00:50 +00:00
|
|
|
/**
|
|
|
|
* Notifies the feature jwt that the action {@link SET_CONFIG} or
|
2017-06-05 18:19:25 +00:00
|
|
|
* {@link SET_LOCATION_URL} is being dispatched within a specific redux
|
2017-04-21 10:00:50 +00:00
|
|
|
* {@code store}.
|
|
|
|
*
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Store} store - The redux store in which the specified {@code action}
|
2017-04-21 10:00:50 +00:00
|
|
|
* is being dispatched.
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
2017-04-21 10:00:50 +00:00
|
|
|
* specified {@code action} to the specified {@code store}.
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Action} action - The redux action {@code SET_CONFIG} or
|
2017-05-09 05:15:43 +00:00
|
|
|
* {@code SET_LOCATION_URL} which is being dispatched in the specified
|
2017-04-21 10:00:50 +00:00
|
|
|
* {@code store}.
|
|
|
|
* @private
|
|
|
|
* @returns {Object} The new state that is the result of the reduction of the
|
|
|
|
* specified {@code action}.
|
|
|
|
*/
|
2017-05-09 05:15:43 +00:00
|
|
|
function _setConfigOrLocationURL({ dispatch, getState }, next, action) {
|
2017-04-21 10:00:50 +00:00
|
|
|
const result = next(action);
|
|
|
|
|
2017-05-09 05:15:43 +00:00
|
|
|
const { locationURL } = getState()['features/base/connection'];
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-10-17 22:10:42 +00:00
|
|
|
dispatch(
|
|
|
|
setJWT(locationURL ? parseJWTFromURLParams(locationURL) : undefined));
|
2017-04-21 10:00:50 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
|
2017-06-05 18:19:25 +00:00
|
|
|
* within a specific redux {@code store}.
|
2017-04-21 10:00:50 +00:00
|
|
|
*
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Store} store - The redux store in which the specified {@code action}
|
2017-04-21 10:00:50 +00:00
|
|
|
* is being dispatched.
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Dispatch} next - The redux dispatch function to dispatch the
|
2017-04-21 10:00:50 +00:00
|
|
|
* specified {@code action} to the specified {@code store}.
|
2017-06-05 18:19:25 +00:00
|
|
|
* @param {Action} action - The redux action {@code SET_JWT} which is being
|
2017-04-21 10:00:50 +00:00
|
|
|
* dispatched in the specified {@code store}.
|
|
|
|
* @private
|
|
|
|
* @returns {Object} The new state that is the result of the reduction of the
|
|
|
|
* specified {@code action}.
|
|
|
|
*/
|
2017-06-05 18:19:25 +00:00
|
|
|
function _setJWT(store, next, action) {
|
2017-04-21 10:00:50 +00:00
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
|
const { jwt, type, ...actionPayload } = action;
|
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
if (!Object.keys(actionPayload).length) {
|
|
|
|
if (jwt) {
|
|
|
|
const {
|
|
|
|
enableUserRolesBasedOnToken
|
|
|
|
} = store.getState()['features/base/config'];
|
|
|
|
|
|
|
|
action.isGuest = !enableUserRolesBasedOnToken;
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
const jwtPayload = jwtDecode(jwt);
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
if (jwtPayload) {
|
|
|
|
const { context, iss } = jwtPayload;
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
action.jwt = jwt;
|
|
|
|
action.issuer = iss;
|
|
|
|
if (context) {
|
|
|
|
const user = _user2participant(context.user);
|
2017-04-21 10:00:50 +00:00
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
action.callee = context.callee;
|
|
|
|
action.group = context.group;
|
|
|
|
action.server = context.server;
|
|
|
|
action.user = user;
|
2017-10-06 17:52:23 +00:00
|
|
|
|
2018-06-15 18:10:22 +00:00
|
|
|
user && _overwriteLocalParticipant(
|
|
|
|
store, { ...user,
|
|
|
|
features: context.features });
|
2017-10-06 17:52:23 +00:00
|
|
|
}
|
2017-04-21 10:00:50 +00:00
|
|
|
}
|
2017-10-13 19:31:05 +00:00
|
|
|
} else if (typeof APP === 'undefined') {
|
|
|
|
// The logic of restoring JWT overrides make sense only on mobile.
|
|
|
|
// On Web it should eventually be restored from storage, but there's
|
|
|
|
// no such use case yet.
|
|
|
|
|
|
|
|
const { user } = store.getState()['features/base/jwt'];
|
|
|
|
|
|
|
|
user && _undoOverwriteLocalParticipant(store, user);
|
2017-10-06 17:52:23 +00:00
|
|
|
}
|
2017-04-21 10:00:50 +00:00
|
|
|
}
|
|
|
|
|
2017-11-23 16:26:47 +00:00
|
|
|
return _maybeSetCalleeInfoVisible(store, next, action);
|
2017-04-21 10:00:50 +00:00
|
|
|
}
|
2017-10-13 19:31:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Undoes/resets the values overwritten by {@link _overwriteLocalParticipant}
|
|
|
|
* by either clearing them or setting to default values. Only the values that
|
|
|
|
* have not changed since the overwrite happened will be restored.
|
|
|
|
*
|
|
|
|
* NOTE Once it is possible to edit and save participant properties, this
|
|
|
|
* function should restore values from the storage instead.
|
|
|
|
*
|
|
|
|
* @param {Store} store - The redux store.
|
|
|
|
* @param {Object} localParticipant - The {@code Participant} structure used
|
|
|
|
* previously to {@link _overwriteLocalParticipant}.
|
|
|
|
* @private
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
function _undoOverwriteLocalParticipant(
|
|
|
|
{ dispatch, getState },
|
|
|
|
{ avatarURL, name, email }) {
|
|
|
|
let localParticipant;
|
|
|
|
|
|
|
|
if ((avatarURL || name || email)
|
|
|
|
&& (localParticipant = getLocalParticipant(getState))) {
|
Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.
Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.
Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:
If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-22 21:47:43 +00:00
|
|
|
const newProperties: Object = {
|
|
|
|
id: localParticipant.id,
|
|
|
|
local: true
|
|
|
|
};
|
2017-10-13 19:31:05 +00:00
|
|
|
|
|
|
|
if (avatarURL === localParticipant.avatarURL) {
|
|
|
|
newProperties.avatarURL = undefined;
|
|
|
|
}
|
|
|
|
if (email === localParticipant.email) {
|
|
|
|
newProperties.email = undefined;
|
|
|
|
}
|
|
|
|
if (name === localParticipant.name) {
|
2017-10-18 18:15:49 +00:00
|
|
|
newProperties.name = undefined;
|
2017-10-13 19:31:05 +00:00
|
|
|
}
|
2018-06-15 18:10:22 +00:00
|
|
|
newProperties.features = undefined;
|
|
|
|
|
2017-10-13 19:31:05 +00:00
|
|
|
dispatch(participantUpdated(newProperties));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts the JWT {@code context.user} structure to the {@code Participant}
|
|
|
|
* structure stored in the redux state base/participants.
|
|
|
|
*
|
|
|
|
* @param {Object} user - The JWT {@code context.user} structure to convert.
|
|
|
|
* @private
|
|
|
|
* @returns {{
|
|
|
|
* avatarURL: ?string,
|
|
|
|
* email: ?string,
|
2017-11-29 02:57:13 +00:00
|
|
|
* id: ?string,
|
2017-10-13 19:31:05 +00:00
|
|
|
* name: ?string
|
|
|
|
* }}
|
|
|
|
*/
|
2017-11-29 02:57:13 +00:00
|
|
|
function _user2participant({ avatar, avatarUrl, email, id, name }) {
|
2017-10-13 19:31:05 +00:00
|
|
|
const participant = {};
|
|
|
|
|
|
|
|
if (typeof avatarUrl === 'string') {
|
|
|
|
participant.avatarURL = avatarUrl.trim();
|
|
|
|
} else if (typeof avatar === 'string') {
|
|
|
|
participant.avatarURL = avatar.trim();
|
|
|
|
}
|
|
|
|
if (typeof email === 'string') {
|
|
|
|
participant.email = email.trim();
|
|
|
|
}
|
2017-11-29 02:57:13 +00:00
|
|
|
if (typeof id === 'string') {
|
|
|
|
participant.id = id.trim();
|
|
|
|
}
|
2017-10-13 19:31:05 +00:00
|
|
|
if (typeof name === 'string') {
|
|
|
|
participant.name = name.trim();
|
|
|
|
}
|
|
|
|
|
|
|
|
return Object.keys(participant).length ? participant : undefined;
|
|
|
|
}
|