"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7561/react/features/lobby/middleware.ts" (29 Sep 2023, 14434 Bytes) of package /linux/misc/jitsi-meet-7561.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) TypeScript source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 import i18n from 'i18next';
2 import { batch } from 'react-redux';
3 import { AnyAction } from 'redux';
4
5 import { IStore } from '../app/types';
6 import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app/actionTypes';
7 import {
8 CONFERENCE_FAILED,
9 CONFERENCE_JOINED
10 } from '../base/conference/actionTypes';
11 import { conferenceWillJoin } from '../base/conference/actions';
12 import {
13 JitsiConferenceErrors,
14 JitsiConferenceEvents
15 } from '../base/lib-jitsi-meet';
16 import {
17 getFirstLoadableAvatarUrl,
18 getParticipantDisplayName
19 } from '../base/participants/functions';
20 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
21 import StateListenerRegistry from '../base/redux/StateListenerRegistry';
22 import {
23 playSound,
24 registerSound,
25 unregisterSound
26 } from '../base/sounds/actions';
27 import { isTestModeEnabled } from '../base/testing/functions';
28 import { BUTTON_TYPES } from '../base/ui/constants.any';
29 import { openChat } from '../chat/actions';
30 import {
31 handleLobbyChatInitialized,
32 removeLobbyChatParticipant
33 } from '../chat/actions.any';
34 import { hideNotification, showNotification } from '../notifications/actions';
35 import {
36 LOBBY_NOTIFICATION_ID,
37 NOTIFICATION_ICON,
38 NOTIFICATION_TIMEOUT_TYPE,
39 NOTIFICATION_TYPE
40 } from '../notifications/constants';
41 import { INotificationProps } from '../notifications/types';
42 import { open as openParticipantsPane } from '../participants-pane/actions';
43 import { getParticipantsPaneOpen } from '../participants-pane/functions';
44 import { isPrejoinPageVisible, shouldAutoKnock } from '../prejoin/functions';
45
46 import {
47 KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
48 KNOCKING_PARTICIPANT_LEFT
49 } from './actionTypes';
50 import {
51 approveKnockingParticipant,
52 hideLobbyScreen,
53 knockingParticipantLeft,
54 openLobbyScreen,
55 participantIsKnockingOrUpdated,
56 rejectKnockingParticipant,
57 setLobbyMessageListener,
58 setLobbyModeEnabled,
59 setPasswordJoinFailed,
60 startKnocking
61 } from './actions';
62 import { updateLobbyParticipantOnLeave } from './actions.any';
63 import { KNOCKING_PARTICIPANT_SOUND_ID } from './constants';
64 import { getKnockingParticipants, showLobbyChatButton } from './functions';
65 import { KNOCKING_PARTICIPANT_FILE } from './sounds';
66 import { IKnockingParticipant } from './types';
67
68
69 MiddlewareRegistry.register(store => next => action => {
70 switch (action.type) {
71 case APP_WILL_MOUNT:
72 store.dispatch(registerSound(KNOCKING_PARTICIPANT_SOUND_ID, KNOCKING_PARTICIPANT_FILE));
73 break;
74 case APP_WILL_UNMOUNT:
75 store.dispatch(unregisterSound(KNOCKING_PARTICIPANT_SOUND_ID));
76 break;
77 case CONFERENCE_FAILED:
78 return _conferenceFailed(store, next, action);
79 case CONFERENCE_JOINED:
80 return _conferenceJoined(store, next, action);
81 case KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED: {
82 // We need the full update result to be in the store already
83 const result = next(action);
84
85 _findLoadableAvatarForKnockingParticipant(store, action.participant);
86 _handleLobbyNotification(store);
87
88 return result;
89 }
90 case KNOCKING_PARTICIPANT_LEFT: {
91 // We need the full update result to be in the store already
92 const result = next(action);
93
94 _handleLobbyNotification(store);
95
96 return result;
97 }
98 }
99
100 return next(action);
101 });
102
103 /**
104 * Registers a change handler for state['features/base/conference'].conference to
105 * set the event listeners needed for the lobby feature to operate.
106 */
107 StateListenerRegistry.register(
108 state => state['features/base/conference'].conference,
109 (conference, { dispatch, getState }, previousConference) => {
110 if (conference && !previousConference) {
111 conference.on(JitsiConferenceEvents.MEMBERS_ONLY_CHANGED, (enabled: boolean) => {
112 dispatch(setLobbyModeEnabled(enabled));
113 if (enabled) {
114 dispatch(setLobbyMessageListener());
115 }
116 });
117
118 conference.on(JitsiConferenceEvents.LOBBY_USER_JOINED, (id: string, name: string) => {
119 const { soundsParticipantKnocking } = getState()['features/base/settings'];
120
121 batch(() => {
122 dispatch(
123 participantIsKnockingOrUpdated({
124 id,
125 name
126 })
127 );
128 if (soundsParticipantKnocking) {
129 dispatch(playSound(KNOCKING_PARTICIPANT_SOUND_ID));
130 }
131
132 const isParticipantsPaneVisible = getParticipantsPaneOpen(getState());
133
134 if (typeof APP !== 'undefined') {
135 APP.API.notifyKnockingParticipant({
136 id,
137 name
138 });
139 }
140
141 if (isParticipantsPaneVisible || navigator.product === 'ReactNative') {
142 return;
143 }
144
145 _handleLobbyNotification({
146 dispatch,
147 getState
148 });
149 });
150 });
151
152 conference.on(JitsiConferenceEvents.LOBBY_USER_UPDATED, (id: string, participant: IKnockingParticipant) => {
153 dispatch(
154 participantIsKnockingOrUpdated({
155 ...participant,
156 id
157 })
158 );
159 });
160
161 conference.on(JitsiConferenceEvents.LOBBY_USER_LEFT, (id: string) => {
162 batch(() => {
163 dispatch(knockingParticipantLeft(id));
164 dispatch(removeLobbyChatParticipant());
165 dispatch(updateLobbyParticipantOnLeave(id));
166 });
167 });
168
169 conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, (origin: any, sender: any) =>
170 _maybeSendLobbyNotification(origin, sender, {
171 dispatch,
172 getState
173 })
174 );
175 }
176 }
177 );
178
179 /**
180 * Function to handle the lobby notification.
181 *
182 * @param {Object} store - The Redux store.
183 * @returns {void}
184 */
185 function _handleLobbyNotification(store: IStore) {
186 const { dispatch, getState } = store;
187 const knockingParticipants = getKnockingParticipants(getState());
188
189 if (knockingParticipants.length === 0) {
190 dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
191
192 return;
193 }
194
195 let notificationTitle;
196 let customActionNameKey;
197 let customActionHandler;
198 let customActionType;
199 let descriptionKey;
200 let icon;
201
202 if (knockingParticipants.length === 1) {
203 const firstParticipant = knockingParticipants[0];
204 const { disablePolls } = getState()['features/base/config'];
205 const showChat = showLobbyChatButton(firstParticipant)(getState());
206
207 descriptionKey = 'notify.participantWantsToJoin';
208 notificationTitle = firstParticipant.name;
209 icon = NOTIFICATION_ICON.PARTICIPANT;
210 customActionNameKey = [ 'lobby.admit', 'lobby.reject' ];
211 customActionType = [ BUTTON_TYPES.PRIMARY, BUTTON_TYPES.DESTRUCTIVE ];
212 customActionHandler = [ () => batch(() => {
213 dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
214 dispatch(approveKnockingParticipant(firstParticipant.id));
215 }),
216 () => batch(() => {
217 dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
218 dispatch(rejectKnockingParticipant(firstParticipant.id));
219 }) ];
220
221 // This checks if lobby chat button is available
222 // and, if so, it adds it to the customActionNameKey array
223 if (showChat) {
224 customActionNameKey.splice(1, 0, 'lobby.chat');
225 customActionType.splice(1, 0, BUTTON_TYPES.SECONDARY);
226 customActionHandler.splice(1, 0, () => batch(() => {
227 dispatch(handleLobbyChatInitialized(firstParticipant.id));
228 dispatch(openChat({}, disablePolls));
229 }));
230 }
231 } else {
232 descriptionKey = 'notify.participantsWantToJoin';
233 notificationTitle = i18n.t('notify.waitingParticipants', {
234 waitingParticipants: knockingParticipants.length
235 });
236 icon = NOTIFICATION_ICON.PARTICIPANTS;
237 customActionNameKey = [ 'notify.viewLobby' ];
238 customActionType = [ BUTTON_TYPES.PRIMARY ];
239 customActionHandler = [ () => batch(() => {
240 dispatch(hideNotification(LOBBY_NOTIFICATION_ID));
241 dispatch(openParticipantsPane());
242 }) ];
243 }
244
245 dispatch(showNotification({
246 title: notificationTitle,
247 descriptionKey,
248 uid: LOBBY_NOTIFICATION_ID,
249 customActionNameKey,
250 customActionType,
251 customActionHandler,
252 icon
253 }, NOTIFICATION_TIMEOUT_TYPE.STICKY));
254 }
255
256 /**
257 * Function to handle the conference failed event and navigate the user to the lobby screen
258 * based on the failure reason.
259 *
260 * @param {Object} store - The Redux store.
261 * @param {Function} next - The Redux next function.
262 * @param {Object} action - The Redux action.
263 * @returns {Object}
264 */
265 function _conferenceFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
266 const { error } = action;
267 const state = getState();
268 const { membersOnly } = state['features/base/conference'];
269 const nonFirstFailure = Boolean(membersOnly);
270 const { isDisplayNameRequiredError } = state['features/lobby'];
271 const { prejoinConfig } = state['features/base/config'];
272
273 if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
274 if (typeof error.recoverable === 'undefined') {
275 error.recoverable = true;
276 }
277
278 // eslint-disable-next-line @typescript-eslint/no-unused-vars
279 const [ _lobbyJid, lobbyWaitingForHost ] = error.params;
280
281 const result = next(action);
282
283 dispatch(openLobbyScreen());
284
285 // if there was an error about display name and pre-join is not enabled
286 if (shouldAutoKnock(state) || (isDisplayNameRequiredError && !prejoinConfig?.enabled) || lobbyWaitingForHost) {
287 dispatch(startKnocking());
288 }
289
290 // In case of wrong password we need to be in the right state if in the meantime someone allows us to join
291 if (nonFirstFailure) {
292 dispatch(conferenceWillJoin(membersOnly));
293 }
294
295 dispatch(setPasswordJoinFailed(nonFirstFailure));
296
297 return result;
298 } else if (error.name === JitsiConferenceErrors.DISPLAY_NAME_REQUIRED) {
299 const [ isLobbyEnabled ] = error.params;
300
301 const result = next(action);
302
303 // if the error is due to required display name because lobby is enabled for the room
304 // if not showing the prejoin page then show lobby UI
305 if (isLobbyEnabled && !isPrejoinPageVisible(state)) {
306 dispatch(openLobbyScreen());
307 }
308
309 return result;
310 }
311
312 dispatch(hideLobbyScreen());
313
314 if (error.name === JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED) {
315 dispatch(
316 showNotification({
317 appearance: NOTIFICATION_TYPE.ERROR,
318 hideErrorSupportLink: true,
319 titleKey: 'lobby.joinRejectedTitle',
320 descriptionKey: 'lobby.joinRejectedMessage'
321 }, NOTIFICATION_TIMEOUT_TYPE.LONG)
322 );
323 }
324
325 return next(action);
326 }
327
328 /**
329 * Handles cleanup of lobby state when a conference is joined.
330 *
331 * @param {Object} store - The Redux store.
332 * @param {Function} next - The Redux next function.
333 * @param {Object} action - The Redux action.
334 * @returns {Object}
335 */
336 function _conferenceJoined({ dispatch }: IStore, next: Function, action: AnyAction) {
337 dispatch(hideLobbyScreen());
338
339 return next(action);
340 }
341
342 /**
343 * Finds the loadable avatar URL and updates the participant accordingly.
344 *
345 * @param {Object} store - The Redux store.
346 * @param {Object} participant - The knocking participant.
347 * @returns {void}
348 */
349 function _findLoadableAvatarForKnockingParticipant(store: IStore, { id }: { id: string; }) {
350 const { dispatch, getState } = store;
351 const updatedParticipant = getState()['features/lobby'].knockingParticipants.find(p => p.id === id);
352 const { disableThirdPartyRequests } = getState()['features/base/config'];
353
354 if (!disableThirdPartyRequests && updatedParticipant && !updatedParticipant.loadableAvatarUrl) {
355 getFirstLoadableAvatarUrl(updatedParticipant, store).then((result: { isUsingCORS: boolean; src: string; }) => {
356 if (result) {
357 const { isUsingCORS, src } = result;
358
359 dispatch(
360 participantIsKnockingOrUpdated({
361 loadableAvatarUrl: src,
362 id,
363 isUsingCORS
364 })
365 );
366 }
367 });
368 }
369 }
370
371 /**
372 * Check the endpoint message that arrived through the conference and
373 * sends a lobby notification, if the message belongs to the feature.
374 *
375 * @param {Object} origin - The origin (initiator) of the message.
376 * @param {Object} message - The actual message.
377 * @param {Object} store - The Redux store.
378 * @returns {void}
379 */
380 function _maybeSendLobbyNotification(origin: any, message: any, { dispatch, getState }: IStore) {
381 if (!origin?._id || message?.type !== 'lobby-notify') {
382 return;
383 }
384
385 const notificationProps: INotificationProps = {
386 descriptionArguments: {
387 originParticipantName: getParticipantDisplayName(getState, origin._id),
388 targetParticipantName: message.name
389 },
390 titleKey: 'lobby.notificationTitle'
391 };
392
393 switch (message.event) {
394 case 'LOBBY-ENABLED':
395 notificationProps.descriptionKey = `lobby.notificationLobby${message.value ? 'En' : 'Dis'}abled`;
396 break;
397 case 'LOBBY-ACCESS-GRANTED':
398 notificationProps.descriptionKey = 'lobby.notificationLobbyAccessGranted';
399 break;
400 case 'LOBBY-ACCESS-DENIED':
401 notificationProps.descriptionKey = 'lobby.notificationLobbyAccessDenied';
402 break;
403 }
404
405 dispatch(
406 showNotification(
407 notificationProps,
408 isTestModeEnabled(getState()) ? NOTIFICATION_TIMEOUT_TYPE.STICKY : NOTIFICATION_TIMEOUT_TYPE.MEDIUM
409 )
410 );
411 }