diff --git a/react/features/base/connection/actions.any.ts b/react/features/base/connection/actions.any.ts index 0d14747a9e5..07121bef290 100644 --- a/react/features/base/connection/actions.any.ts +++ b/react/features/base/connection/actions.any.ts @@ -1,7 +1,6 @@ import _ from 'lodash'; import { IReduxState, IStore } from '../../app/types'; -import { setPrejoinDisplayNameRequired } from '../../prejoin/actions.any'; import { conferenceLeft, conferenceWillLeave } from '../conference/actions'; import { getCurrentConference } from '../conference/functions'; import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet'; @@ -232,15 +231,6 @@ export function _connectInternal(id?: string, password?: string) { JitsiConnectionEvents.CONNECTION_FAILED, _onConnectionFailed); - /** - * Marks the display name for the prejoin screen as required. - * This can happen if a user tries to join a room with lobby enabled. - */ - connection.addEventListener( - JitsiConnectionEvents.DISPLAY_NAME_REQUIRED, - () => dispatch(setPrejoinDisplayNameRequired()) - ); - /** * Unsubscribe the connection instance from * {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events. diff --git a/react/features/lobby/actionTypes.ts b/react/features/lobby/actionTypes.ts index a7a209a1afe..99073bdf377 100644 --- a/react/features/lobby/actionTypes.ts +++ b/react/features/lobby/actionTypes.ts @@ -36,4 +36,4 @@ export const SET_PASSWORD_JOIN_FAILED = 'SET_PASSWORD_JOIN_FAILED'; /** * Action type to remove chattingWithModerator field */ - export const REMOVE_LOBBY_CHAT_WITH_MODERATOR = 'REMOVE_LOBBY_CHAT_WITH_MODERATOR'; \ No newline at end of file + export const REMOVE_LOBBY_CHAT_WITH_MODERATOR = 'REMOVE_LOBBY_CHAT_WITH_MODERATOR'; diff --git a/react/features/lobby/actions.any.ts b/react/features/lobby/actions.any.ts index 44409e00653..f5846b29daf 100644 --- a/react/features/lobby/actions.any.ts +++ b/react/features/lobby/actions.any.ts @@ -8,6 +8,7 @@ import { LOBBY_CHAT_MESSAGE } from '../chat/constants'; import { handleLobbyMessageReceived } from '../chat/middleware'; import { hideNotification, showNotification } from '../notifications/actions'; import { LOBBY_NOTIFICATION_ID } from '../notifications/constants'; +import { joinConference } from '../prejoin/actions'; import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED, @@ -205,6 +206,18 @@ export function startKnocking() { return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => { const state = getState(); const { membersOnly } = state['features/base/conference']; + + if (!membersOnly) { + + // no membersOnly, this means we got lobby screen shown as someone + // tried to join a conference that has lobby enabled without setting display name + // join conference should trigger the lobby/member_only path after setting the display name + // this is possible only for web, where we can join without a prejoin screen + dispatch(joinConference()); + + return; + } + const localParticipant = getLocalParticipant(state); dispatch(conferenceWillJoin(membersOnly)); @@ -406,3 +419,4 @@ export function setLobbyMessageListener() { }); }; } + diff --git a/react/features/lobby/components/AbstractLobbyScreen.tsx b/react/features/lobby/components/AbstractLobbyScreen.tsx index c0575ad639f..22a7467c751 100644 --- a/react/features/lobby/components/AbstractLobbyScreen.tsx +++ b/react/features/lobby/components/AbstractLobbyScreen.tsx @@ -27,6 +27,11 @@ export interface IProps { */ _deviceStatusVisible: boolean; + /** + * Indicates whether the message that display name is required is shown. + */ + _isDisplayNameRequiredActive: boolean; + /** * True if moderator initiated a chat session with the participant. */ @@ -435,7 +440,7 @@ export function _mapStateToProps(state: IReduxState) { const participantId = localParticipant?.id; const inviteEnabledFlag = getFeatureFlag(state, INVITE_ENABLED, true); const { disableInviteFunctions } = state['features/base/config']; - const { knocking, passwordJoinFailed } = state['features/lobby']; + const { isDisplayNameRequiredError, knocking, passwordJoinFailed } = state['features/lobby']; const { iAmSipGateway } = state['features/base/config']; const { disableLobbyPassword } = getSecurityUiConfig(state); const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions; @@ -445,6 +450,7 @@ export function _mapStateToProps(state: IReduxState) { return { _deviceStatusVisible: deviceStatusVisible, + _isDisplayNameRequiredActive: Boolean(isDisplayNameRequiredError), _knocking: knocking, _lobbyChatMessages: messages, _lobbyMessageRecipient: lobbyMessageRecipient?.name, diff --git a/react/features/lobby/components/web/LobbyScreen.tsx b/react/features/lobby/components/web/LobbyScreen.tsx index f68e64556a6..fd5a4b985d9 100644 --- a/react/features/lobby/components/web/LobbyScreen.tsx +++ b/react/features/lobby/components/web/LobbyScreen.tsx @@ -153,16 +153,25 @@ class LobbyScreen extends AbstractLobbyScreen { */ _renderParticipantInfo() { const { displayName } = this.state; - const { t } = this.props; + const { _isDisplayNameRequiredActive, t } = this.props; + const showError = _isDisplayNameRequiredActive && !displayName; return ( - + <> + + + { showError &&
{t('prejoin.errorMissingName')}
} + ); } diff --git a/react/features/lobby/middleware.ts b/react/features/lobby/middleware.ts index a4aa545aa6f..8da1c725a0c 100644 --- a/react/features/lobby/middleware.ts +++ b/react/features/lobby/middleware.ts @@ -41,7 +41,7 @@ import { import { INotificationProps } from '../notifications/types'; import { open as openParticipantsPane } from '../participants-pane/actions'; import { getParticipantsPaneOpen } from '../participants-pane/functions'; -import { shouldAutoKnock } from '../prejoin/functions'; +import { isPrejoinPageVisible, shouldAutoKnock } from '../prejoin/functions'; import { KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED, @@ -267,6 +267,8 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio const state = getState(); const { membersOnly } = state['features/base/conference']; const nonFirstFailure = Boolean(membersOnly); + const { isDisplayNameRequiredError } = state['features/lobby']; + const { prejoinConfig } = state['features/base/config']; if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) { if (typeof error.recoverable === 'undefined') { @@ -277,7 +279,8 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio dispatch(openLobbyScreen()); - if (shouldAutoKnock(state)) { + // if there was an error about display name and pre-join is not enabled + if (shouldAutoKnock(state) || (isDisplayNameRequiredError && !prejoinConfig?.enabled)) { dispatch(startKnocking()); } @@ -288,6 +291,18 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio dispatch(setPasswordJoinFailed(nonFirstFailure)); + return result; + } else if (error.name === JitsiConferenceErrors.DISPLAY_NAME_REQUIRED) { + const [ isLobbyEnabled ] = error.params; + + const result = next(action); + + // if the error is due to required display name because lobby is enabled for the room + // if not showing the prejoin page then show lobby UI + if (isLobbyEnabled && !isPrejoinPageVisible(state)) { + dispatch(openLobbyScreen()); + } + return result; } diff --git a/react/features/lobby/reducer.ts b/react/features/lobby/reducer.ts index a2d494a6dad..6aa5105b086 100644 --- a/react/features/lobby/reducer.ts +++ b/react/features/lobby/reducer.ts @@ -1,4 +1,10 @@ -import { CONFERENCE_JOINED, CONFERENCE_LEFT, SET_PASSWORD } from '../base/conference/actionTypes'; +import { + CONFERENCE_FAILED, + CONFERENCE_JOINED, + CONFERENCE_LEFT, + SET_PASSWORD +} from '../base/conference/actionTypes'; +import { JitsiConferenceErrors } from '../base/lib-jitsi-meet'; import ReducerRegistry from '../base/redux/ReducerRegistry'; import { @@ -14,6 +20,7 @@ import { import { IKnockingParticipant } from './types'; const DEFAULT_STATE = { + isDisplayNameRequiredError: false, knocking: false, knockingParticipants: [], lobbyEnabled: false, @@ -22,6 +29,12 @@ const DEFAULT_STATE = { }; export interface ILobbyState { + + /** + * A conference error when we tried to join into a room with no display name + * when lobby is enabled in the room. + */ + isDisplayNameRequiredError: boolean; knocking: boolean; knockingParticipants: IKnockingParticipant[]; lobbyEnabled: boolean; @@ -39,6 +52,16 @@ export interface ILobbyState { */ ReducerRegistry.register('features/lobby', (state = DEFAULT_STATE, action): ILobbyState => { switch (action.type) { + case CONFERENCE_FAILED: { + if (action.error.name === JitsiConferenceErrors.DISPLAY_NAME_REQUIRED) { + return { + ...state, + isDisplayNameRequiredError: true + }; + } + + return state; + } case CONFERENCE_JOINED: case CONFERENCE_LEFT: return { diff --git a/react/features/prejoin/actionTypes.ts b/react/features/prejoin/actionTypes.ts index e4bb715752c..97c6e07642b 100644 --- a/react/features/prejoin/actionTypes.ts +++ b/react/features/prejoin/actionTypes.ts @@ -19,11 +19,6 @@ export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS'; */ export const SET_SKIP_PREJOIN_RELOAD = 'SET_SKIP_PREJOIN_RELOAD'; -/** - * Action type used to set the mandatory stance of the prejoin display name. - */ -export const SET_PREJOIN_DISPLAY_NAME_REQUIRED = 'SET_PREJOIN_DISPLAY_NAME_REQUIRED'; - /** * Action type to set the country to dial out to. */ diff --git a/react/features/prejoin/actions.any.ts b/react/features/prejoin/actions.any.ts deleted file mode 100644 index 393553c3ee5..00000000000 --- a/react/features/prejoin/actions.any.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - SET_PREJOIN_DISPLAY_NAME_REQUIRED -} from './actionTypes'; - -/** - * Action used to set the stance of the display name. - * - * @returns {Object} - */ -export function setPrejoinDisplayNameRequired() { - return { - type: SET_PREJOIN_DISPLAY_NAME_REQUIRED - }; -} diff --git a/react/features/prejoin/actions.native.ts b/react/features/prejoin/actions.native.ts new file mode 100644 index 00000000000..af074375488 --- /dev/null +++ b/react/features/prejoin/actions.native.ts @@ -0,0 +1,14 @@ +import { IStore } from '../app/types'; + +/** + * Action used to start the conference. + * + * @param {Object} options - The config options that override the default ones (if any). + * @param {boolean} _ignoreJoiningInProgress - If true we won't check the joiningInProgress flag. + * @returns {Function} + */ +export function joinConference(options?: Object, _ignoreJoiningInProgress = false) { + // eslint-disable-next-line @typescript-eslint/no-empty-function + return async function(_dispatch: IStore['dispatch'], _getState: IStore['getState']) { + }; +} diff --git a/react/features/prejoin/actions.web.ts b/react/features/prejoin/actions.web.ts index 20169941007..fa10c8396c6 100644 --- a/react/features/prejoin/actions.web.ts +++ b/react/features/prejoin/actions.web.ts @@ -66,8 +66,6 @@ const STATUS_REQ_FREQUENCY = 2000; */ const STATUS_REQ_CAP = 45; -export * from './actions.any'; - /** * Polls for status change after dial out. * Changes dialog message based on response, closes the dialog if there is an error, diff --git a/react/features/prejoin/functions.ts b/react/features/prejoin/functions.ts index 396853155cd..fd10399aeab 100644 --- a/react/features/prejoin/functions.ts +++ b/react/features/prejoin/functions.ts @@ -35,7 +35,7 @@ export function isDeviceStatusVisible(state: IReduxState): boolean { * @returns {boolean} */ export function isDisplayNameRequired(state: IReduxState): boolean { - return Boolean(state['features/prejoin']?.isDisplayNameRequired + return Boolean(state['features/lobby']?.isDisplayNameRequiredError || state['features/base/config']?.requireDisplayName); } diff --git a/react/features/prejoin/reducer.ts b/react/features/prejoin/reducer.ts index 3c66259d54c..6d61d0bb5e8 100644 --- a/react/features/prejoin/reducer.ts +++ b/react/features/prejoin/reducer.ts @@ -10,7 +10,6 @@ import { SET_JOIN_BY_PHONE_DIALOG_VISIBLITY, SET_PRECALL_TEST_RESULTS, SET_PREJOIN_DEVICE_ERRORS, - SET_PREJOIN_DISPLAY_NAME_REQUIRED, SET_PREJOIN_PAGE_VISIBILITY, SET_SKIP_PREJOIN_RELOAD } from './actionTypes'; @@ -26,7 +25,6 @@ const DEFAULT_STATE = { }, dialOutNumber: '', dialOutStatus: 'prejoin.dialing', - isDisplayNameRequired: false, name: '', rawError: '', showPrejoin: true, @@ -45,7 +43,6 @@ export interface IPrejoinState { }; dialOutNumber: string; dialOutStatus: string; - isDisplayNameRequired: boolean; joiningInProgress?: boolean; name: string; precallTestResults?: { @@ -143,13 +140,6 @@ ReducerRegistry.register( }; } - case SET_PREJOIN_DISPLAY_NAME_REQUIRED: { - return { - ...state, - isDisplayNameRequired: true - }; - } - default: return state; } diff --git a/resources/prosody-plugins/mod_muc_lobby_rooms.lua b/resources/prosody-plugins/mod_muc_lobby_rooms.lua index 54105da0e05..75896ed2d3d 100644 --- a/resources/prosody-plugins/mod_muc_lobby_rooms.lua +++ b/resources/prosody-plugins/mod_muc_lobby_rooms.lua @@ -400,6 +400,17 @@ process_host_module(main_muc_component_config, function(host_module, host) end end + -- Check for display name if missing return an error + local displayName = stanza:get_child_text('nick', 'http://jabber.org/protocol/nick'); + if not displayName or #displayName == 0 then + local reply = st.error_reply(stanza, 'modify', 'not-acceptable'); + reply.tags[1].attr.code = '406'; + reply:tag('displayname-required', { xmlns = 'http://jitsi.org/jitmeet', lobby = 'true' }):up():up(); + + event.origin.send(reply:tag('x', {xmlns = MUC_NS})); + return true; + end + -- we want to add the custom lobbyroom field to fill in the lobby room jid local invitee = event.stanza.attr.from; local affiliation = room:get_affiliation(invitee);