488 lines
16 KiB
TypeScript
488 lines
16 KiB
TypeScript
import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock/constants';
|
|
import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection/actionTypes';
|
|
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
|
import ReducerRegistry from '../redux/ReducerRegistry';
|
|
import { assign, set } from '../redux/functions';
|
|
|
|
import {
|
|
AUTH_STATUS_CHANGED,
|
|
CONFERENCE_FAILED,
|
|
CONFERENCE_JOINED,
|
|
CONFERENCE_LEFT,
|
|
CONFERENCE_LOCAL_SUBJECT_CHANGED,
|
|
CONFERENCE_SUBJECT_CHANGED,
|
|
CONFERENCE_TIMESTAMP_CHANGED,
|
|
CONFERENCE_WILL_JOIN,
|
|
CONFERENCE_WILL_LEAVE,
|
|
LOCK_STATE_CHANGED,
|
|
P2P_STATUS_CHANGED,
|
|
SET_FOLLOW_ME,
|
|
SET_OBFUSCATED_ROOM,
|
|
SET_PASSWORD,
|
|
SET_PENDING_SUBJECT_CHANGE,
|
|
SET_ROOM,
|
|
SET_START_MUTED_POLICY,
|
|
SET_START_REACTIONS_MUTED
|
|
} from './actionTypes';
|
|
import { isRoomValid } from './functions';
|
|
|
|
const DEFAULT_STATE = {
|
|
conference: undefined,
|
|
e2eeSupported: undefined,
|
|
joining: undefined,
|
|
leaving: undefined,
|
|
locked: undefined,
|
|
membersOnly: undefined,
|
|
password: undefined,
|
|
passwordRequired: undefined
|
|
};
|
|
|
|
export interface IJitsiConference {
|
|
addTrack: Function;
|
|
avModerationApprove: Function;
|
|
avModerationReject: Function;
|
|
createVideoSIPGWSession: Function;
|
|
disableAVModeration: Function;
|
|
enableAVModeration: Function;
|
|
getBreakoutRooms: Function;
|
|
getLocalTracks: Function;
|
|
grantOwner: Function;
|
|
isAVModerationSupported: Function;
|
|
isCallstatsEnabled: Function;
|
|
isEndConferenceSupported: Function;
|
|
isLobbySupported: Function;
|
|
kickParticipant: Function;
|
|
muteParticipant: Function;
|
|
myLobbyUserId: Function;
|
|
myUserId: Function;
|
|
on: Function;
|
|
removeTrack: Function;
|
|
replaceTrack: Function;
|
|
sendCommand: Function;
|
|
sendEndpointMessage: Function;
|
|
sendFeedback: Function;
|
|
sendLobbyMessage: Function;
|
|
sessionId: string;
|
|
setDisplayName: Function;
|
|
setLocalParticipantProperty: Function;
|
|
}
|
|
|
|
export interface IConferenceState {
|
|
authEnabled?: boolean;
|
|
authLogin?: string;
|
|
authRequired?: Object;
|
|
conference?: IJitsiConference;
|
|
conferenceTimestamp?: number;
|
|
e2eeSupported?: boolean;
|
|
error?: Error;
|
|
followMeEnabled?: boolean;
|
|
joining?: Object;
|
|
leaving?: Object;
|
|
localSubject?: string;
|
|
locked?: string;
|
|
membersOnly?: Object;
|
|
obfuscatedRoom?: string;
|
|
obfuscatedRoomSource?: string;
|
|
p2p?: Object;
|
|
password?: string;
|
|
passwordRequired?: Object;
|
|
pendingSubjectChange?: string;
|
|
room?: string;
|
|
startAudioMutedPolicy?: boolean;
|
|
startReactionsMuted?: boolean;
|
|
startVideoMutedPolicy?: boolean;
|
|
subject?: string;
|
|
}
|
|
|
|
/**
|
|
* Listen for actions that contain the conference object, so that it can be
|
|
* stored for use by other action creators.
|
|
*/
|
|
ReducerRegistry.register<IConferenceState>('features/base/conference',
|
|
(state = DEFAULT_STATE, action): IConferenceState => {
|
|
switch (action.type) {
|
|
case AUTH_STATUS_CHANGED:
|
|
return _authStatusChanged(state, action);
|
|
|
|
case CONFERENCE_FAILED:
|
|
return _conferenceFailed(state, action);
|
|
|
|
case CONFERENCE_JOINED:
|
|
return _conferenceJoined(state, action);
|
|
|
|
case CONFERENCE_SUBJECT_CHANGED:
|
|
return set(state, 'subject', action.subject);
|
|
|
|
case CONFERENCE_LOCAL_SUBJECT_CHANGED:
|
|
return set(state, 'localSubject', action.localSubject);
|
|
|
|
case CONFERENCE_TIMESTAMP_CHANGED:
|
|
return set(state, 'conferenceTimestamp', action.conferenceTimestamp);
|
|
|
|
case CONFERENCE_LEFT:
|
|
case CONFERENCE_WILL_LEAVE:
|
|
return _conferenceLeftOrWillLeave(state, action);
|
|
|
|
case CONFERENCE_WILL_JOIN:
|
|
return _conferenceWillJoin(state, action);
|
|
|
|
case CONNECTION_WILL_CONNECT:
|
|
return set(state, 'authRequired', undefined);
|
|
|
|
case LOCK_STATE_CHANGED:
|
|
return _lockStateChanged(state, action);
|
|
|
|
case P2P_STATUS_CHANGED:
|
|
return _p2pStatusChanged(state, action);
|
|
|
|
case SET_FOLLOW_ME:
|
|
return set(state, 'followMeEnabled', action.enabled);
|
|
|
|
case SET_START_REACTIONS_MUTED:
|
|
return set(state, 'startReactionsMuted', action.muted);
|
|
|
|
case SET_LOCATION_URL:
|
|
return set(state, 'room', undefined);
|
|
|
|
case SET_OBFUSCATED_ROOM:
|
|
return { ...state,
|
|
obfuscatedRoom: action.obfuscatedRoom,
|
|
obfuscatedRoomSource: action.obfuscatedRoomSource
|
|
};
|
|
|
|
case SET_PASSWORD:
|
|
return _setPassword(state, action);
|
|
|
|
case SET_PENDING_SUBJECT_CHANGE:
|
|
return set(state, 'pendingSubjectChange', action.subject);
|
|
|
|
case SET_ROOM:
|
|
return _setRoom(state, action);
|
|
|
|
case SET_START_MUTED_POLICY:
|
|
return {
|
|
...state,
|
|
startAudioMutedPolicy: action.startAudioMutedPolicy,
|
|
startVideoMutedPolicy: action.startVideoMutedPolicy
|
|
};
|
|
}
|
|
|
|
return state;
|
|
});
|
|
|
|
/**
|
|
* Reduces a specific Redux action AUTH_STATUS_CHANGED of the feature
|
|
* base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action AUTH_STATUS_CHANGED to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _authStatusChanged(state: IConferenceState,
|
|
{ authEnabled, authLogin }: { authEnabled: boolean; authLogin: string; }) {
|
|
return assign(state, {
|
|
authEnabled,
|
|
authLogin
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action CONFERENCE_FAILED of the feature
|
|
* base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action CONFERENCE_FAILED to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _conferenceFailed(state: IConferenceState, { conference, error }: { conference: Object; error: Error; }) {
|
|
// The current (similar to getCurrentConference in
|
|
// base/conference/functions.any.js) conference which is joining or joined:
|
|
const conference_ = state.conference || state.joining;
|
|
|
|
if (conference_ && conference_ !== conference) {
|
|
return state;
|
|
}
|
|
|
|
let authRequired;
|
|
let membersOnly;
|
|
let passwordRequired;
|
|
|
|
switch (error.name) {
|
|
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED:
|
|
authRequired = conference;
|
|
break;
|
|
|
|
case JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED:
|
|
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR:
|
|
membersOnly = conference;
|
|
break;
|
|
|
|
case JitsiConferenceErrors.PASSWORD_REQUIRED:
|
|
passwordRequired = conference;
|
|
break;
|
|
}
|
|
|
|
return assign(state, {
|
|
authRequired,
|
|
conference: undefined,
|
|
e2eeSupported: undefined,
|
|
error,
|
|
joining: undefined,
|
|
leaving: undefined,
|
|
|
|
/**
|
|
* The indicator of how the conference/room is locked. If falsy, the
|
|
* conference/room is unlocked; otherwise, it's either
|
|
* {@code LOCKED_LOCALLY} or {@code LOCKED_REMOTELY}.
|
|
*
|
|
* @type {string}
|
|
*/
|
|
locked: passwordRequired ? LOCKED_REMOTELY : undefined,
|
|
membersOnly,
|
|
password: undefined,
|
|
|
|
/**
|
|
* The JitsiConference instance which requires a password to join.
|
|
*
|
|
* @type {JitsiConference}
|
|
*/
|
|
passwordRequired
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action CONFERENCE_JOINED of the feature
|
|
* base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action CONFERENCE_JOINED to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _conferenceJoined(state: IConferenceState, { conference }: { conference: any; }) {
|
|
// FIXME The indicator which determines whether a JitsiConference is locked
|
|
// i.e. password-protected is private to lib-jitsi-meet. However, the
|
|
// library does not fire LOCK_STATE_CHANGED upon joining a JitsiConference
|
|
// with a password.
|
|
// FIXME Technically JitsiConference.room is a private field.
|
|
const locked = conference.room?.locked ? LOCKED_REMOTELY : undefined;
|
|
|
|
return assign(state, {
|
|
authRequired: undefined,
|
|
|
|
/**
|
|
* The JitsiConference instance represented by the Redux state of the
|
|
* feature base/conference.
|
|
*
|
|
* @type {JitsiConference}
|
|
*/
|
|
conference,
|
|
|
|
e2eeSupported: conference.isE2EESupported(),
|
|
|
|
joining: undefined,
|
|
membersOnly: undefined,
|
|
leaving: undefined,
|
|
|
|
/**
|
|
* The indicator which determines whether the conference is locked.
|
|
*
|
|
* @type {boolean}
|
|
*/
|
|
locked,
|
|
passwordRequired: undefined
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific redux action {@link CONFERENCE_LEFT} or
|
|
* {@link CONFERENCE_WILL_LEAVE} for the feature base/conference.
|
|
*
|
|
* @param {Object} state - The redux state of the feature base/conference.
|
|
* @param {Action} action - The redux action {@code CONFERENCE_LEFT} or
|
|
* {@code CONFERENCE_WILL_LEAVE} to reduce.
|
|
* @private
|
|
* @returns {Object} The next/new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _conferenceLeftOrWillLeave(state: IConferenceState, { conference, type }:
|
|
{ conference: Object; type: string; }) {
|
|
const nextState = { ...state };
|
|
|
|
// The redux action CONFERENCE_LEFT is the last time that we should be
|
|
// hearing from a JitsiConference instance.
|
|
//
|
|
// The redux action CONFERENCE_WILL_LEAVE represents the order of the user
|
|
// to leave a JitsiConference instance. From the user's perspective, there's
|
|
// no going back (with respect to the instance itself). The app will perform
|
|
// due clean-up like leaving the associated room, but the instance is no
|
|
// longer the focus of the attention of the user and, consequently, the app.
|
|
for (const p in state) {
|
|
if (state[p as keyof IConferenceState] === conference) {
|
|
nextState[p as keyof IConferenceState] = undefined;
|
|
|
|
switch (p) {
|
|
case 'conference':
|
|
case 'passwordRequired':
|
|
// XXX Clear/unset locked & password for a conference which has
|
|
// been LOCKED_LOCALLY or LOCKED_REMOTELY.
|
|
delete nextState.locked;
|
|
delete nextState.password;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type === CONFERENCE_WILL_LEAVE) {
|
|
// A CONFERENCE_WILL_LEAVE is of further consequence only if it is
|
|
// expected i.e. if the specified conference is joining or joined.
|
|
if (conference === state.joining || conference === state.conference) {
|
|
/**
|
|
* The JitsiConference instance which is currently in the process of
|
|
* being left.
|
|
*
|
|
* @type {JitsiConference}
|
|
*/
|
|
nextState.leaving = conference;
|
|
}
|
|
}
|
|
|
|
return nextState;
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action CONFERENCE_WILL_JOIN of the feature
|
|
* base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action CONFERENCE_WILL_JOIN to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _conferenceWillJoin(state: IConferenceState, { conference }: { conference: Object; }) {
|
|
return assign(state, {
|
|
error: undefined,
|
|
joining: conference
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action LOCK_STATE_CHANGED of the feature
|
|
* base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action LOCK_STATE_CHANGED to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _lockStateChanged(state: IConferenceState, { conference, locked }: { conference: Object; locked: boolean; }) {
|
|
if (state.conference !== conference) {
|
|
return state;
|
|
}
|
|
|
|
return assign(state, {
|
|
locked: locked ? state.locked || LOCKED_REMOTELY : undefined,
|
|
password: locked ? state.password : undefined
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action P2P_STATUS_CHANGED of the feature
|
|
* base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action P2P_STATUS_CHANGED to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _p2pStatusChanged(state: IConferenceState, action: any) {
|
|
return set(state, 'p2p', action.p2p);
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action SET_PASSWORD to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _setPassword(state: IConferenceState, { conference, method, password }: {
|
|
conference: any; method: Object; password: string; }) {
|
|
switch (method) {
|
|
case conference.join:
|
|
return assign(state, {
|
|
// 1. The JitsiConference which transitions away from
|
|
// passwordRequired MUST remain in the redux state
|
|
// features/base/conference until it transitions into
|
|
// conference; otherwise, there is a span of time during which
|
|
// the redux state does not even know that there is a
|
|
// JitsiConference whatsoever.
|
|
//
|
|
// 2. The redux action setPassword will attempt to join the
|
|
// JitsiConference so joining is an appropriate transitional
|
|
// redux state.
|
|
//
|
|
// 3. The redux action setPassword will perform the same check
|
|
// before it proceeds with the re-join.
|
|
joining: state.conference ? state.joining : conference,
|
|
locked: LOCKED_REMOTELY,
|
|
|
|
/**
|
|
* The password with which the conference is to be joined.
|
|
*
|
|
* @type {string}
|
|
*/
|
|
password
|
|
});
|
|
|
|
case conference.lock:
|
|
return assign(state, {
|
|
locked: password ? LOCKED_LOCALLY : undefined,
|
|
password
|
|
});
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Reduces a specific Redux action SET_ROOM of the feature base/conference.
|
|
*
|
|
* @param {Object} state - The Redux state of the feature base/conference.
|
|
* @param {Action} action - The Redux action SET_ROOM to reduce.
|
|
* @private
|
|
* @returns {Object} The new state of the feature base/conference after the
|
|
* reduction of the specified action.
|
|
*/
|
|
function _setRoom(state: IConferenceState, action: any) {
|
|
let { room } = action;
|
|
|
|
if (!isRoomValid(room)) {
|
|
// Technically, there are multiple values which don't represent valid
|
|
// room names. Practically, each of them is as bad as the rest of them
|
|
// because we can't use any of them to join a conference.
|
|
room = undefined;
|
|
}
|
|
|
|
/**
|
|
* The name of the room of the conference (to be) joined.
|
|
*
|
|
* @type {string}
|
|
*/
|
|
return assign(state, {
|
|
error: undefined,
|
|
room
|
|
});
|
|
}
|
|
|