Ver código fonte

feat: Drops connection on prejoin screen. (#13538)

* feat: Drops connection on prejoin screen.

Refactors connection logic to reuse already existing logic from mobile. Connection is now established just before joining the room.
Fixes some authentication logic with Login and Logout button in Profile tab.

* squash: Drops createInitialLocalTracksAndConnect as it no longer connects.

* squash: Shows an error on mobile and redirects to default.

* squash: Fixes review comments.

* squash: Fixes joining with prejoin disabled.

* squash: Fixes adding initial local tracks.

* squash: Fixes comments.

* squash: Drop no longer used method.

* squash: Fixes old web code imported into mobile builds.

* squash: Drop unused prop.

* squash: Drops recoverable flag on REDIRECT.

* squash: Drops unused variable and fix connection access.

* squash: Xmpp connect returns promise again.

* squash: Execute xmpp connect and creating local tracks in parallel.

* squash: Moves notification about problem jwt.

* squash: Moves startConference to conference.js for the no prejoin case.

And move the startConference in prejoin feature for the prejoin case.

* squash: Fix passing filtered tracks when starting conference with no prejoin.

* squash: Fix clearing listeners on connection established.

Keeps mobile behaviour after merging web and mobile.

* squash: Drops unused code.
factor2
Дамян Минков 2 anos atrás
pai
commit
bc23f9cd33
Nenhuma conta vinculada ao e-mail do autor do commit
38 arquivos alterados com 706 adições e 1462 exclusões
  1. 54
    244
      conference.js
  2. 0
    209
      connection.js
  3. 1
    2
      lang/main.json
  4. 4
    78
      modules/UI/UI.js
  5. 0
    227
      modules/UI/authentication/AuthHandler.js
  6. 0
    28
      modules/UI/authentication/LoginDialog.js
  7. 0
    61
      modules/UI/util/MessageHandler.js
  8. 0
    12
      modules/UI/util/UIUtil.js
  9. 1
    0
      react/features/app/middlewares.any.ts
  10. 0
    1
      react/features/app/middlewares.native.ts
  11. 1
    1
      react/features/app/middlewares.web.ts
  12. 18
    0
      react/features/authentication/actionTypes.ts
  13. 11
    1
      react/features/authentication/actions.any.ts
  14. 9
    2
      react/features/authentication/actions.native.ts
  15. 28
    23
      react/features/authentication/actions.web.ts
  16. 5
    32
      react/features/authentication/components/web/LoginDialog.tsx
  17. 2
    9
      react/features/authentication/components/web/WaitForOwnerDialog.tsx
  18. 79
    24
      react/features/authentication/middleware.ts
  19. 0
    152
      react/features/authentication/middleware.web.ts
  20. 9
    0
      react/features/base/conference/actionTypes.ts
  21. 16
    12
      react/features/base/conference/actions.ts
  22. 86
    84
      react/features/base/conference/middleware.any.ts
  23. 2
    1
      react/features/base/conference/middleware.web.ts
  24. 3
    0
      react/features/base/conference/reducer.ts
  25. 198
    1
      react/features/base/connection/actions.any.ts
  26. 8
    182
      react/features/base/connection/actions.native.ts
  27. 40
    25
      react/features/base/connection/actions.web.ts
  28. 30
    0
      react/features/base/connection/middleware.web.ts
  29. 1
    1
      react/features/base/tracks/actions.any.ts
  30. 24
    0
      react/features/conference/actions.web.ts
  31. 4
    3
      react/features/conference/components/web/Conference.tsx
  32. 14
    0
      react/features/prejoin/actions.any.ts
  33. 27
    32
      react/features/prejoin/actions.web.ts
  34. 3
    3
      react/features/room-lock/middleware.ts
  35. 10
    0
      react/features/settings/actions.native.ts
  36. 12
    9
      react/features/settings/components/web/ProfileTab.tsx
  37. 6
    1
      react/features/video-layout/middleware.web.ts
  38. 0
    2
      service/UI/UIEvents.js

+ 54
- 244
conference.js Ver arquivo

4
 import Logger from '@jitsi/logger';
4
 import Logger from '@jitsi/logger';
5
 import EventEmitter from 'events';
5
 import EventEmitter from 'events';
6
 
6
 
7
-import { openConnection } from './connection';
8
 import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
7
 import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
9
 import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from './modules/UI/UIErrors';
8
 import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from './modules/UI/UIErrors';
10
-import AuthHandler from './modules/UI/authentication/AuthHandler';
11
-import UIUtil from './modules/UI/util/UIUtil';
12
-import VideoLayout from './modules/UI/videolayout/VideoLayout';
13
 import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
9
 import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
14
 import Recorder from './modules/recorder/Recorder';
10
 import Recorder from './modules/recorder/Recorder';
15
 import { createTaskQueue } from './modules/util/helpers';
11
 import { createTaskQueue } from './modules/util/helpers';
37
     conferenceSubjectChanged,
33
     conferenceSubjectChanged,
38
     conferenceTimestampChanged,
34
     conferenceTimestampChanged,
39
     conferenceUniqueIdSet,
35
     conferenceUniqueIdSet,
40
-    conferenceWillJoin,
36
+    conferenceWillInit,
41
     conferenceWillLeave,
37
     conferenceWillLeave,
42
     dataChannelClosed,
38
     dataChannelClosed,
43
     dataChannelOpened,
39
     dataChannelOpened,
57
     commonUserJoinedHandling,
53
     commonUserJoinedHandling,
58
     commonUserLeftHandling,
54
     commonUserLeftHandling,
59
     getConferenceOptions,
55
     getConferenceOptions,
60
-    getVisitorOptions,
61
-    restoreConferenceOptions,
62
     sendLocalParticipant
56
     sendLocalParticipant
63
 } from './react/features/base/conference/functions';
57
 } from './react/features/base/conference/functions';
64
-import { overwriteConfig } from './react/features/base/config/actions';
65
 import { getReplaceParticipant } from './react/features/base/config/functions';
58
 import { getReplaceParticipant } from './react/features/base/config/functions';
59
+import { connect } from './react/features/base/connection/actions.web';
66
 import {
60
 import {
67
     checkAndNotifyForNewDevice,
61
     checkAndNotifyForNewDevice,
68
     getAvailableDevices,
62
     getAvailableDevices,
77
 import {
71
 import {
78
     JitsiConferenceErrors,
72
     JitsiConferenceErrors,
79
     JitsiConferenceEvents,
73
     JitsiConferenceEvents,
80
-    JitsiConnectionErrors,
81
-    JitsiConnectionEvents,
82
     JitsiE2ePingEvents,
74
     JitsiE2ePingEvents,
83
     JitsiMediaDevicesEvents,
75
     JitsiMediaDevicesEvents,
84
     JitsiTrackErrors,
76
     JitsiTrackErrors,
85
     JitsiTrackEvents,
77
     JitsiTrackEvents,
86
     browser
78
     browser
87
 } from './react/features/base/lib-jitsi-meet';
79
 } from './react/features/base/lib-jitsi-meet';
88
-import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
89
 import {
80
 import {
90
     gumPending,
81
     gumPending,
91
     setAudioAvailable,
82
     setAudioAvailable,
145
 import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
136
 import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
146
 import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
137
 import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
147
 import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
138
 import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
148
-import { hideNotification, showNotification, showWarningNotification } from './react/features/notifications/actions';
139
+import {
140
+    hideNotification,
141
+    showErrorNotification,
142
+    showNotification,
143
+    showWarningNotification
144
+} from './react/features/notifications/actions';
149
 import {
145
 import {
150
     DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
146
     DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
151
     NOTIFICATION_TIMEOUT_TYPE
147
     NOTIFICATION_TIMEOUT_TYPE
153
 import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
149
 import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
154
 import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
150
 import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
155
 import { suspendDetected } from './react/features/power-monitor/actions';
151
 import { suspendDetected } from './react/features/power-monitor/actions';
156
-import { initPrejoin, makePrecallTest, setJoiningInProgress } from './react/features/prejoin/actions';
152
+import { initPrejoin, makePrecallTest } from './react/features/prejoin/actions';
157
 import { isPrejoinPageVisible } from './react/features/prejoin/functions';
153
 import { isPrejoinPageVisible } from './react/features/prejoin/functions';
158
 import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
154
 import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
159
 import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
155
 import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
164
 import { endpointMessageReceived } from './react/features/subtitles/actions.any';
160
 import { endpointMessageReceived } from './react/features/subtitles/actions.any';
165
 import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
161
 import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
166
 import { muteLocal } from './react/features/video-menu/actions.any';
162
 import { muteLocal } from './react/features/video-menu/actions.any';
167
-import { setIAmVisitor } from './react/features/visitors/actions';
168
 import { iAmVisitor } from './react/features/visitors/functions';
163
 import { iAmVisitor } from './react/features/visitors/functions';
169
 import UIEvents from './service/UI/UIEvents';
164
 import UIEvents from './service/UI/UIEvents';
170
 
165
 
173
 const eventEmitter = new EventEmitter();
168
 const eventEmitter = new EventEmitter();
174
 
169
 
175
 let room;
170
 let room;
176
-let connection;
177
-
178
-/**
179
- * The promise is used when the prejoin screen is shown.
180
- * While the user configures the devices the connection can be made.
181
- *
182
- * @type {Promise<Object>}
183
- * @private
184
- */
185
-let _connectionPromise;
186
-
187
-/**
188
- * We are storing the resolve function of a Promise that waits for the _connectionPromise to be created. This is needed
189
- * when the prejoin button was pressed before the conference object was initialized and the _connectionPromise has not
190
- * been initialized when we tried to execute prejoinStart. In this case in prejoinStart we create a new Promise, assign
191
- * the resolve function to this variable and wait for the promise to resolve before we continue. The
192
- * _onConnectionPromiseCreated will be called once the _connectionPromise is created.
193
- */
194
-let _onConnectionPromiseCreated;
195
 
171
 
196
 /*
172
 /*
197
  * Logic to open a desktop picker put on the window global for
173
  * Logic to open a desktop picker put on the window global for
213
     ETHERPAD: 'etherpad'
189
     ETHERPAD: 'etherpad'
214
 };
190
 };
215
 
191
 
216
-/**
217
- * Open Connection. When authentication failed it shows auth dialog.
218
- * @param roomName the room name to use
219
- * @returns Promise<JitsiConnection>
220
- */
221
-function connect(roomName) {
222
-    return openConnection({
223
-        retry: true,
224
-        roomName
225
-    })
226
-    .catch(err => {
227
-        if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
228
-            APP.UI.notifyTokenAuthFailed();
229
-        } else {
230
-            APP.UI.notifyConnectionFailed(err);
231
-        }
232
-        throw err;
233
-    });
234
-}
235
-
236
 /**
192
 /**
237
  * Share data to other users.
193
  * Share data to other users.
238
  * @param command the command
194
  * @param command the command
326
             break;
282
             break;
327
         }
283
         }
328
 
284
 
329
-        // not enough rights to create conference
330
-        case JitsiConferenceErrors.AUTHENTICATION_REQUIRED: {
331
-
332
-            const replaceParticipant = getReplaceParticipant(APP.store.getState());
333
-
334
-            // Schedule reconnect to check if someone else created the room.
335
-            this.reconnectTimeout = setTimeout(() => {
336
-                APP.store.dispatch(conferenceWillJoin(room));
337
-                room.join(null, replaceParticipant);
338
-            }, 5000);
339
-
340
-            const { password }
341
-                = APP.store.getState()['features/base/conference'];
342
-
343
-            AuthHandler.requireAuth(room, password);
344
-
345
-            break;
346
-        }
347
-
348
         case JitsiConferenceErrors.RESERVATION_ERROR: {
285
         case JitsiConferenceErrors.RESERVATION_ERROR: {
349
             const [ code, msg ] = params;
286
             const [ code, msg ] = params;
350
 
287
 
351
-            APP.UI.notifyReservationError(code, msg);
352
-            break;
353
-        }
354
-
355
-        case JitsiConferenceErrors.REDIRECTED: {
356
-            const newConfig = getVisitorOptions(APP.store.getState(), params);
357
-
358
-            if (!newConfig) {
359
-                logger.warn('Not redirected missing params');
360
-                break;
361
-            }
362
-
363
-            const [ vnode ] = params;
364
-
365
-            APP.store.dispatch(overwriteConfig(newConfig))
366
-                .then(() => this._conference.leaveRoom())
367
-                .then(() => APP.store.dispatch(setIAmVisitor(Boolean(vnode))))
368
-
369
-                // we do not clear local tracks on error, so we need to manually clear them
370
-                .then(() => APP.store.dispatch(destroyLocalTracks()))
371
-                .then(() => {
372
-                    // Reset VideoLayout. It's destroyed in features/video-layout/middleware.web.js so re-initialize it.
373
-                    VideoLayout.initLargeVideo();
374
-                    VideoLayout.resizeVideoArea();
375
-
376
-                    connect(this._conference.roomName).then(con => {
377
-                        this._conference.startConference(con, []);
378
-                    });
379
-                });
380
-
288
+            APP.store.dispatch(showErrorNotification({
289
+                descriptionArguments: {
290
+                    code,
291
+                    msg
292
+                },
293
+                descriptionKey: 'dialog.reservationErrorMsg',
294
+                titleKey: 'dialog.reservationError'
295
+            }, NOTIFICATION_TIMEOUT_TYPE.LONG));
381
             break;
296
             break;
382
         }
297
         }
383
 
298
 
384
         case JitsiConferenceErrors.GRACEFUL_SHUTDOWN:
299
         case JitsiConferenceErrors.GRACEFUL_SHUTDOWN:
385
-            APP.UI.notifyGracefulShutdown();
300
+            APP.store.dispatch(showErrorNotification({
301
+                descriptionKey: 'dialog.gracefulShutdown',
302
+                titleKey: 'dialog.serviceUnavailable'
303
+            }, NOTIFICATION_TIMEOUT_TYPE.LONG));
386
             break;
304
             break;
387
 
305
 
388
         // FIXME FOCUS_DISCONNECTED is a confusing event name.
306
         // FIXME FOCUS_DISCONNECTED is a confusing event name.
408
             // FIXME the conference should be stopped by the library and not by
326
             // FIXME the conference should be stopped by the library and not by
409
             // the app. Both the errors above are unrecoverable from the library
327
             // the app. Both the errors above are unrecoverable from the library
410
             // perspective.
328
             // perspective.
411
-            room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).then(() => connection.disconnect());
329
+            room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).then(() => APP.connection.disconnect());
412
             break;
330
             break;
413
 
331
 
414
-        case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
415
-            APP.UI.notifyMaxUsersLimitReached();
416
-
417
-            // in case of max users(it can be from a visitor node), let's restore
418
-            // oldConfig if any as we will be back to the main prosody
419
-            const newConfig = restoreConferenceOptions(APP.store.getState());
420
-
421
-            if (newConfig) {
422
-                APP.store.dispatch(overwriteConfig(newConfig))
423
-                    .then(() => this._conference.leaveRoom())
424
-                    .then(() => {
425
-                        _connectionPromise = connect(this._conference.roomName);
426
-
427
-                        return _connectionPromise;
428
-                    })
429
-                    .then(con => {
430
-                        APP.connection = connection = con;
431
-                    });
432
-            }
433
-
434
-            break;
435
-        }
436
         case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
332
         case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
437
             APP.store.dispatch(reloadWithStoredParams());
333
             APP.store.dispatch(reloadWithStoredParams());
438
             break;
334
             break;
488
         return Promise.resolve();
384
         return Promise.resolve();
489
     };
385
     };
490
 
386
 
491
-    if (!connection) {
387
+    if (!APP.connection) {
492
         return onDisconnected();
388
         return onDisconnected();
493
     }
389
     }
494
 
390
 
495
-    return connection.disconnect().then(onDisconnected, onDisconnected);
391
+    return APP.connection.disconnect().then(onDisconnected, onDisconnected);
496
 }
392
 }
497
 
393
 
498
 /**
394
 /**
510
     APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
406
     APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
511
 }
407
 }
512
 
408
 
513
-/**
514
- * Handles CONNECTION_FAILED events from lib-jitsi-meet.
515
- *
516
- * @param {JitsiConnectionError} error - The reported error.
517
- * @returns {void}
518
- * @private
519
- */
520
-function _connectionFailedHandler(error) {
521
-    if (isFatalJitsiConnectionError(error)) {
522
-        APP.connection.removeEventListener(
523
-            JitsiConnectionEvents.CONNECTION_FAILED,
524
-            _connectionFailedHandler);
525
-        if (room) {
526
-            APP.store.dispatch(conferenceWillLeave(room));
527
-            room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR);
528
-        }
529
-    }
530
-}
531
-
532
 export default {
409
 export default {
533
     /**
410
     /**
534
      * Flag used to delay modification of the muted status of local media tracks
411
      * Flag used to delay modification of the muted status of local media tracks
547
     /**
424
     /**
548
      * Returns an object containing a promise which resolves with the created tracks &
425
      * Returns an object containing a promise which resolves with the created tracks &
549
      * the errors resulting from that process.
426
      * the errors resulting from that process.
550
-     *
427
+     * @param {object} options
428
+     * @param {boolean} options.startAudioOnly=false - if <tt>true</tt> then
429
+     * only audio track will be created and the audio only mode will be turned
430
+     * on.
431
+     * @param {boolean} options.startScreenSharing=false - if <tt>true</tt>
432
+     * should start with screensharing instead of camera video.
433
+     * @param {boolean} options.startWithAudioMuted - will start the conference
434
+     * without any audio tracks.
435
+     * @param {boolean} options.startWithVideoMuted - will start the conference
436
+     * without any video tracks.
551
      * @returns {Promise<JitsiLocalTrack[]>, Object}
437
      * @returns {Promise<JitsiLocalTrack[]>, Object}
552
      */
438
      */
553
     createInitialLocalTracks(options = {}) {
439
     createInitialLocalTracks(options = {}) {
725
         }
611
         }
726
     },
612
     },
727
 
613
 
728
-    /**
729
-     * Creates local media tracks and connects to a room. Will show error
730
-     * dialogs in case accessing the local microphone and/or camera failed. Will
731
-     * show guidance overlay for users on how to give access to camera and/or
732
-     * microphone.
733
-     * @param {string} roomName
734
-     * @param {object} options
735
-     * @param {boolean} options.startAudioOnly=false - if <tt>true</tt> then
736
-     * only audio track will be created and the audio only mode will be turned
737
-     * on.
738
-     * @param {boolean} options.startScreenSharing=false - if <tt>true</tt>
739
-     * should start with screensharing instead of camera video.
740
-     * @param {boolean} options.startWithAudioMuted - will start the conference
741
-     * without any audio tracks.
742
-     * @param {boolean} options.startWithVideoMuted - will start the conference
743
-     * without any video tracks.
744
-     * @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
745
-     */
746
-    createInitialLocalTracksAndConnect(roomName, options = {}) {
747
-        const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
748
-
749
-        return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
750
-            .then(([ tracks, con ]) => {
751
-
752
-                this._displayErrorsForCreateInitialLocalTracks(errors);
753
-
754
-                return [ tracks, con ];
755
-            });
756
-    },
757
-
758
-    startConference(con, tracks) {
614
+    startConference(tracks) {
759
         tracks.forEach(track => {
615
         tracks.forEach(track => {
760
             if ((track.isAudioTrack() && this.isLocalAudioMuted())
616
             if ((track.isAudioTrack() && this.isLocalAudioMuted())
761
                 || (track.isVideoTrack() && this.isLocalVideoMuted())) {
617
                 || (track.isVideoTrack() && this.isLocalVideoMuted())) {
768
             }
624
             }
769
         });
625
         });
770
 
626
 
771
-        con.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _connectionFailedHandler);
772
-        APP.connection = connection = con;
773
-
774
         this._createRoom(tracks);
627
         this._createRoom(tracks);
775
 
628
 
776
         // if user didn't give access to mic or camera or doesn't have
629
         // if user didn't give access to mic or camera or doesn't have
860
         };
713
         };
861
 
714
 
862
         if (isPrejoinPageVisible(state)) {
715
         if (isPrejoinPageVisible(state)) {
863
-            _connectionPromise = connect(roomName).then(c => {
864
-                // We want to initialize it early, in case of errors to be able to gather logs.
865
-                APP.connection = c;
866
-
867
-                return c;
868
-            });
869
-
870
-            if (_onConnectionPromiseCreated) {
871
-                _onConnectionPromiseCreated();
872
-            }
873
-
874
             APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
716
             APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
875
 
717
 
876
             const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
718
             const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
897
             return this._setLocalAudioVideoStreams(tracks);
739
             return this._setLocalAudioVideoStreams(tracks);
898
         }
740
         }
899
 
741
 
900
-        const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
742
+        const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
901
 
743
 
902
-        this._initDeviceList(true);
744
+        return Promise.all([
745
+            tryCreateLocalTracks.then(tr => {
746
+                this._displayErrorsForCreateInitialLocalTracks(errors);
903
 
747
 
904
-        const filteredTracks = handleInitialTracks(initialOptions, tracks);
748
+                return tr;
749
+            }).then(tr => {
750
+                this._initDeviceList(true);
905
 
751
 
906
-        setGUMPendingStateOnFailedTracks(filteredTracks);
752
+                const filteredTracks = handleInitialTracks(initialOptions, tr);
907
 
753
 
908
-        return this.startConference(con, filteredTracks);
909
-    },
754
+                setGUMPendingStateOnFailedTracks(filteredTracks);
910
 
755
 
911
-    /**
912
-     * Joins conference after the tracks have been configured in the prejoin screen.
913
-     *
914
-     * @param {Object[]} tracks - An array with the configured tracks
915
-     * @returns {void}
916
-     */
917
-    async prejoinStart(tracks) {
918
-        if (!_connectionPromise) {
919
-            // The conference object isn't initialized yet. Wait for the promise to initialise.
920
-            await new Promise(resolve => {
921
-                _onConnectionPromiseCreated = resolve;
922
-            });
923
-            _onConnectionPromiseCreated = undefined;
924
-        }
925
-
926
-        let con;
927
-
928
-        try {
929
-            con = await _connectionPromise;
930
-            this.startConference(con, tracks);
931
-        } catch (error) {
932
-            logger.error(`An error occurred while trying to join a meeting from the prejoin screen: ${error}`);
933
-            APP.store.dispatch(setJoiningInProgress(false));
934
-        }
756
+                return filteredTracks;
757
+            }),
758
+            APP.store.dispatch(connect())
759
+        ]).then(([ tracks, _ ]) => {
760
+            this.startConference(tracks).catch(logger.error);
761
+        });
935
     },
762
     },
936
 
763
 
937
     /**
764
     /**
1413
      * Used by the Breakout Rooms feature to join a breakout room or go back to the main room.
1240
      * Used by the Breakout Rooms feature to join a breakout room or go back to the main room.
1414
      */
1241
      */
1415
     async joinRoom(roomName, options) {
1242
     async joinRoom(roomName, options) {
1416
-        // Reset VideoLayout. It's destroyed in features/video-layout/middleware.web.js so re-initialize it.
1417
-        VideoLayout.initLargeVideo();
1418
-        VideoLayout.resizeVideoArea();
1243
+        APP.store.dispatch(conferenceWillInit());
1419
 
1244
 
1420
         // Restore initial state.
1245
         // Restore initial state.
1421
         this._localTracksInitialized = false;
1246
         this._localTracksInitialized = false;
1440
     },
1265
     },
1441
 
1266
 
1442
     _createRoom(localTracks) {
1267
     _createRoom(localTracks) {
1443
-        room = connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
1268
+        room = APP.connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
1444
 
1269
 
1445
         // Filter out the tracks that are muted (except on Safari).
1270
         // Filter out the tracks that are muted (except on Safari).
1446
         const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
1271
         const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
1787
             titleKey = 'notify.screenShareNoAudioTitle';
1612
             titleKey = 'notify.screenShareNoAudioTitle';
1788
         }
1613
         }
1789
 
1614
 
1790
-        APP.UI.messageHandler.showError({
1615
+        APP.store.dispatch(showErrorNotification({
1791
             descriptionKey,
1616
             descriptionKey,
1792
             titleKey
1617
             titleKey
1793
-        });
1618
+        }, NOTIFICATION_TIMEOUT_TYPE.LONG));
1794
     },
1619
     },
1795
 
1620
 
1796
     /**
1621
     /**
2184
             this.hangup(true);
2009
             this.hangup(true);
2185
         });
2010
         });
2186
 
2011
 
2187
-        // logout
2188
-        APP.UI.addListener(UIEvents.LOGOUT, () => {
2189
-            AuthHandler.logout(room).then(url => {
2190
-                if (url) {
2191
-                    UIUtil.redirect(url);
2192
-                } else {
2193
-                    this.hangup(true);
2194
-                }
2195
-            });
2196
-        });
2197
-
2198
-        APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
2199
-            AuthHandler.authenticate(room);
2200
-        });
2201
-
2202
         APP.UI.addListener(
2012
         APP.UI.addListener(
2203
             UIEvents.VIDEO_DEVICE_CHANGED,
2013
             UIEvents.VIDEO_DEVICE_CHANGED,
2204
             cameraDeviceId => {
2014
             cameraDeviceId => {

+ 0
- 209
connection.js Ver arquivo

1
-/* global APP, JitsiMeetJS, config */
2
-
3
-import { jitsiLocalStorage } from '@jitsi/js-utils';
4
-import Logger from '@jitsi/logger';
5
-
6
-import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
7
-import { LoginDialog } from './react/features/authentication/components';
8
-import { isTokenAuthEnabled } from './react/features/authentication/functions';
9
-import {
10
-    connectionEstablished,
11
-    connectionFailed,
12
-    constructOptions
13
-} from './react/features/base/connection/actions.web';
14
-import { openDialog } from './react/features/base/dialog/actions';
15
-import { setJWT } from './react/features/base/jwt/actions';
16
-import {
17
-    JitsiConnectionErrors,
18
-    JitsiConnectionEvents
19
-} from './react/features/base/lib-jitsi-meet';
20
-import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
21
-import { getCustomerDetails } from './react/features/jaas/actions.any';
22
-import { getJaasJWT, isVpaasMeeting } from './react/features/jaas/functions';
23
-import {
24
-    setPrejoinDisplayNameRequired
25
-} from './react/features/prejoin/actions';
26
-const logger = Logger.getLogger(__filename);
27
-
28
-/**
29
- * The feature announced so we can distinguish jibri participants.
30
- *
31
- * @type {string}
32
- */
33
-export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
34
-
35
-/**
36
- * Try to open connection using provided credentials.
37
- * @param {string} [id]
38
- * @param {string} [password]
39
- * @returns {Promise<JitsiConnection>} connection if
40
- * everything is ok, else error.
41
- */
42
-export async function connect(id, password) {
43
-    const state = APP.store.getState();
44
-    let { jwt } = state['features/base/jwt'];
45
-    const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
46
-
47
-    if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
48
-        await APP.store.dispatch(getCustomerDetails());
49
-
50
-        if (!jwt) {
51
-            jwt = await getJaasJWT(state);
52
-            APP.store.dispatch(setJWT(jwt));
53
-        }
54
-    }
55
-
56
-    const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
57
-
58
-    if (config.iAmRecorder) {
59
-        connection.addFeature(DISCO_JIBRI_FEATURE);
60
-    }
61
-
62
-    return new Promise((resolve, reject) => {
63
-        connection.addEventListener(
64
-            JitsiConnectionEvents.CONNECTION_ESTABLISHED,
65
-            handleConnectionEstablished);
66
-        connection.addEventListener(
67
-            JitsiConnectionEvents.CONNECTION_FAILED,
68
-            handleConnectionFailed);
69
-        connection.addEventListener(
70
-            JitsiConnectionEvents.CONNECTION_FAILED,
71
-            connectionFailedHandler);
72
-        connection.addEventListener(
73
-            JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
74
-            displayNameRequiredHandler
75
-        );
76
-
77
-        /* eslint-disable max-params */
78
-        /**
79
-         *
80
-         */
81
-        function connectionFailedHandler(error, message, credentials, details) {
82
-        /* eslint-enable max-params */
83
-            APP.store.dispatch(
84
-                connectionFailed(
85
-                    connection, {
86
-                        credentials,
87
-                        details,
88
-                        message,
89
-                        name: error
90
-                    }));
91
-
92
-            if (isFatalJitsiConnectionError(error)) {
93
-                connection.removeEventListener(
94
-                    JitsiConnectionEvents.CONNECTION_FAILED,
95
-                    connectionFailedHandler);
96
-            }
97
-        }
98
-
99
-        /**
100
-         *
101
-         */
102
-        function unsubscribe() {
103
-            connection.removeEventListener(
104
-                JitsiConnectionEvents.CONNECTION_ESTABLISHED,
105
-                handleConnectionEstablished);
106
-            connection.removeEventListener(
107
-                JitsiConnectionEvents.CONNECTION_FAILED,
108
-                handleConnectionFailed);
109
-        }
110
-
111
-        /**
112
-         *
113
-         */
114
-        function handleConnectionEstablished() {
115
-            APP.store.dispatch(connectionEstablished(connection, Date.now()));
116
-            unsubscribe();
117
-            resolve(connection);
118
-        }
119
-
120
-        /**
121
-         *
122
-         */
123
-        function handleConnectionFailed(err) {
124
-            unsubscribe();
125
-            logger.error('CONNECTION FAILED:', err);
126
-            reject(err);
127
-        }
128
-
129
-        /**
130
-         * Marks the display name for the prejoin screen as required.
131
-         * This can happen if a user tries to join a room with lobby enabled.
132
-         */
133
-        function displayNameRequiredHandler() {
134
-            APP.store.dispatch(setPrejoinDisplayNameRequired());
135
-        }
136
-
137
-        connection.connect({
138
-            id,
139
-            password
140
-        });
141
-    });
142
-}
143
-
144
-/**
145
- * Open JitsiConnection using provided credentials.
146
- * If retry option is true it will show auth dialog on PASSWORD_REQUIRED error.
147
- *
148
- * @param {object} options
149
- * @param {string} [options.id]
150
- * @param {string} [options.password]
151
- * @param {string} [options.roomName]
152
- * @param {boolean} [retry] if we should show auth dialog
153
- * on PASSWORD_REQUIRED error.
154
- *
155
- * @returns {Promise<JitsiConnection>}
156
- */
157
-export function openConnection({ id, password, retry, roomName }) {
158
-    const usernameOverride
159
-        = jitsiLocalStorage.getItem('xmpp_username_override');
160
-    const passwordOverride
161
-        = jitsiLocalStorage.getItem('xmpp_password_override');
162
-
163
-    if (usernameOverride && usernameOverride.length > 0) {
164
-        id = usernameOverride; // eslint-disable-line no-param-reassign
165
-    }
166
-    if (passwordOverride && passwordOverride.length > 0) {
167
-        password = passwordOverride; // eslint-disable-line no-param-reassign
168
-    }
169
-
170
-    return connect(id, password).catch(err => {
171
-        if (retry) {
172
-            const { jwt } = APP.store.getState()['features/base/jwt'];
173
-
174
-            if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
175
-                return requestAuth(roomName);
176
-            }
177
-        }
178
-
179
-        throw err;
180
-    });
181
-}
182
-
183
-/**
184
- * Show Authentication Dialog and try to connect with new credentials.
185
- * If failed to connect because of PASSWORD_REQUIRED error
186
- * then ask for password again.
187
- * @param {string} [roomName] name of the conference room
188
- *
189
- * @returns {Promise<JitsiConnection>}
190
- */
191
-function requestAuth(roomName) {
192
-    const config = APP.store.getState()['features/base/config'];
193
-
194
-    if (isTokenAuthEnabled(config)) {
195
-        // This Promise never resolves as user gets redirected to another URL
196
-        return new Promise(() => redirectToTokenAuthService(roomName));
197
-    }
198
-
199
-    return new Promise(resolve => {
200
-        const onSuccess = connection => {
201
-            resolve(connection);
202
-        };
203
-
204
-        APP.store.dispatch(
205
-            openDialog(LoginDialog, { onSuccess,
206
-                roomName })
207
-        );
208
-    });
209
-}

+ 1
- 2
lang/main.json Ver arquivo

370
         "permissionCameraRequiredError": "Camera permission is required to participate in conferences with video. Please grant it in Settings",
370
         "permissionCameraRequiredError": "Camera permission is required to participate in conferences with video. Please grant it in Settings",
371
         "permissionErrorTitle": "Permission required",
371
         "permissionErrorTitle": "Permission required",
372
         "permissionMicRequiredError": "Microphone permission is required to participate in conferences with audio. Please grant it in Settings",
372
         "permissionMicRequiredError": "Microphone permission is required to participate in conferences with audio. Please grant it in Settings",
373
-        "popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
374
-        "popupErrorTitle": "Pop-up blocked",
375
         "readMore": "more",
373
         "readMore": "more",
376
         "recentlyUsedObjects": "Your recently used objects",
374
         "recentlyUsedObjects": "Your recently used objects",
377
         "recording": "Recording",
375
         "recording": "Recording",
439
         "token": "token",
437
         "token": "token",
440
         "tokenAuthFailed": "Sorry, you're not allowed to join this call.",
438
         "tokenAuthFailed": "Sorry, you're not allowed to join this call.",
441
         "tokenAuthFailedTitle": "Authentication failed",
439
         "tokenAuthFailedTitle": "Authentication failed",
440
+        "tokenAuthUnsupported": "Token URL is not supported.",
442
         "transcribing": "Transcribing",
441
         "transcribing": "Transcribing",
443
         "unlockRoom": "Remove meeting $t(lockRoomPassword)",
442
         "unlockRoom": "Remove meeting $t(lockRoomPassword)",
444
         "user": "User",
443
         "user": "User",

+ 4
- 78
modules/UI/UI.js Ver arquivo

6
 import Logger from '@jitsi/logger';
6
 import Logger from '@jitsi/logger';
7
 import EventEmitter from 'events';
7
 import EventEmitter from 'events';
8
 
8
 
9
+import {
10
+    conferenceWillInit
11
+} from '../../react/features/base/conference/actions';
9
 import { isMobileBrowser } from '../../react/features/base/environment/utils';
12
 import { isMobileBrowser } from '../../react/features/base/environment/utils';
10
 import { setColorAlpha } from '../../react/features/base/util/helpers';
13
 import { setColorAlpha } from '../../react/features/base/util/helpers';
11
 import { setDocumentUrl } from '../../react/features/etherpad/actions';
14
 import { setDocumentUrl } from '../../react/features/etherpad/actions';
24
 import UIEvents from '../../service/UI/UIEvents';
27
 import UIEvents from '../../service/UI/UIEvents';
25
 
28
 
26
 import EtherpadManager from './etherpad/Etherpad';
29
 import EtherpadManager from './etherpad/Etherpad';
27
-import messageHandler from './util/MessageHandler';
28
 import UIUtil from './util/UIUtil';
30
 import UIUtil from './util/UIUtil';
29
 import VideoLayout from './videolayout/VideoLayout';
31
 import VideoLayout from './videolayout/VideoLayout';
30
 
32
 
31
 const logger = Logger.getLogger(__filename);
33
 const logger = Logger.getLogger(__filename);
32
 
34
 
33
-UI.messageHandler = messageHandler;
34
-
35
 const eventEmitter = new EventEmitter();
35
 const eventEmitter = new EventEmitter();
36
 
36
 
37
 UI.eventEmitter = eventEmitter;
37
 UI.eventEmitter = eventEmitter;
58
     return UIUtil.isFullScreen();
58
     return UIUtil.isFullScreen();
59
 };
59
 };
60
 
60
 
61
-/**
62
- * Notify user that server has shut down.
63
- */
64
-UI.notifyGracefulShutdown = function() {
65
-    messageHandler.showError({
66
-        descriptionKey: 'dialog.gracefulShutdown',
67
-        titleKey: 'dialog.serviceUnavailable'
68
-    });
69
-};
70
-
71
-/**
72
- * Notify user that reservation error happened.
73
- */
74
-UI.notifyReservationError = function(code, msg) {
75
-    messageHandler.showError({
76
-        descriptionArguments: {
77
-            code,
78
-            msg
79
-        },
80
-        descriptionKey: 'dialog.reservationErrorMsg',
81
-        titleKey: 'dialog.reservationError'
82
-    });
83
-};
84
-
85
 /**
61
 /**
86
  * Initialize conference UI.
62
  * Initialize conference UI.
87
  */
63
  */
91
 
67
 
92
 /**
68
 /**
93
  * Starts the UI module and initializes all related components.
69
  * Starts the UI module and initializes all related components.
94
- *
95
- * @returns {boolean} true if the UI is ready and the conference should be
96
- * established, false - otherwise (for example in the case of welcome page)
97
  */
70
  */
98
 UI.start = function() {
71
 UI.start = function() {
99
-    VideoLayout.initLargeVideo();
100
-
101
-    // Do not animate the video area on UI start (second argument passed into
102
-    // resizeVideoArea) because the animation is not visible anyway. Plus with
103
-    // the current dom layout, the quality label is part of the video layout and
104
-    // will be seen animating in.
105
-    VideoLayout.resizeVideoArea();
72
+    APP.store.dispatch(conferenceWillInit());
106
 
73
 
107
     if (isMobileBrowser()) {
74
     if (isMobileBrowser()) {
108
         document.body.classList.add('mobile-browser');
75
         document.body.classList.add('mobile-browser');
292
 // Used by torture.
259
 // Used by torture.
293
 UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
260
 UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
294
 
261
 
295
-/**
296
- * Notify user that connection failed.
297
- * @param {string} stropheErrorMsg raw Strophe error message
298
- */
299
-UI.notifyConnectionFailed = function(stropheErrorMsg) {
300
-    let descriptionKey;
301
-    let descriptionArguments;
302
-
303
-    if (stropheErrorMsg) {
304
-        descriptionKey = 'dialog.connectErrorWithMsg';
305
-        descriptionArguments = { msg: stropheErrorMsg };
306
-    } else {
307
-        descriptionKey = 'dialog.connectError';
308
-    }
309
-
310
-    messageHandler.showError({
311
-        descriptionArguments,
312
-        descriptionKey,
313
-        titleKey: 'connection.CONNFAIL'
314
-    });
315
-};
316
-
317
-
318
-/**
319
- * Notify user that maximum users limit has been reached.
320
- */
321
-UI.notifyMaxUsersLimitReached = function() {
322
-    messageHandler.showError({
323
-        hideErrorSupportLink: true,
324
-        descriptionKey: 'dialog.maxUsersLimitReached',
325
-        titleKey: 'dialog.maxUsersLimitReachedTitle'
326
-    });
327
-};
328
-
329
 UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
262
 UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
330
     VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
263
     VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
331
 };
264
 };
337
  */
270
  */
338
 UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
271
 UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
339
 
272
 
340
-UI.notifyTokenAuthFailed = function() {
341
-    messageHandler.showError({
342
-        descriptionKey: 'dialog.tokenAuthFailed',
343
-        titleKey: 'dialog.tokenAuthFailedTitle'
344
-    });
345
-};
346
-
347
 /**
273
 /**
348
  * Update list of available physical devices.
274
  * Update list of available physical devices.
349
  */
275
  */

+ 0
- 227
modules/UI/authentication/AuthHandler.js Ver arquivo

1
-/* global APP */
2
-import Logger from '@jitsi/logger';
3
-
4
-import { openConnection } from '../../../connection';
5
-import {
6
-    openAuthDialog,
7
-    openLoginDialog } from '../../../react/features/authentication/actions.web';
8
-import {
9
-    LoginDialog,
10
-    WaitForOwnerDialog
11
-} from '../../../react/features/authentication/components';
12
-import {
13
-    getTokenAuthUrl,
14
-    isTokenAuthEnabled
15
-} from '../../../react/features/authentication/functions';
16
-import { getReplaceParticipant } from '../../../react/features/base/config/functions';
17
-import { isDialogOpen } from '../../../react/features/base/dialog/functions';
18
-import { setJWT } from '../../../react/features/base/jwt/actions';
19
-import UIUtil from '../util/UIUtil';
20
-
21
-import ExternalLoginDialog from './LoginDialog';
22
-
23
-
24
-let externalAuthWindow;
25
-
26
-const logger = Logger.getLogger(__filename);
27
-
28
-
29
-/**
30
- * Authenticate using external service or just focus
31
- * external auth window if there is one already.
32
- *
33
- * @param {JitsiConference} room
34
- * @param {string} [lockPassword] password to use if the conference is locked
35
- */
36
-function doExternalAuth(room, lockPassword) {
37
-    const config = APP.store.getState()['features/base/config'];
38
-
39
-    if (externalAuthWindow) {
40
-        externalAuthWindow.focus();
41
-
42
-        return;
43
-    }
44
-
45
-    if (room.isJoined()) {
46
-        let getUrl;
47
-
48
-        if (isTokenAuthEnabled(config)) {
49
-            getUrl = Promise.resolve(getTokenAuthUrl(config)(room.getName(), true));
50
-            initJWTTokenListener(room);
51
-        } else {
52
-            getUrl = room.getExternalAuthUrl(true);
53
-        }
54
-        getUrl.then(url => {
55
-            externalAuthWindow = ExternalLoginDialog.showExternalAuthDialog(
56
-                url,
57
-                () => {
58
-                    externalAuthWindow = null;
59
-                    if (!isTokenAuthEnabled(config)) {
60
-                        room.join(lockPassword);
61
-                    }
62
-                }
63
-            );
64
-        });
65
-    } else if (isTokenAuthEnabled(config)) {
66
-        redirectToTokenAuthService(room.getName());
67
-    } else {
68
-        room.getExternalAuthUrl().then(UIUtil.redirect);
69
-    }
70
-}
71
-
72
-/**
73
- * Redirect the user to the token authentication service for the login to be
74
- * performed. Once complete it is expected that the service will bring the user
75
- * back with "?jwt={the JWT token}" query parameter added.
76
- * @param {string} [roomName] the name of the conference room.
77
- */
78
-export function redirectToTokenAuthService(roomName) {
79
-    const config = APP.store.getState()['features/base/config'];
80
-
81
-    // FIXME: This method will not preserve the other URL params that were
82
-    // originally passed.
83
-    UIUtil.redirect(getTokenAuthUrl(config)(roomName, false));
84
-}
85
-
86
-/**
87
- * Initializes 'message' listener that will wait for a JWT token to be received
88
- * from the token authentication service opened in a popup window.
89
- * @param room the name of the conference room.
90
- */
91
-function initJWTTokenListener(room) {
92
-    /**
93
-     *
94
-     */
95
-    function listener({ data, source }) {
96
-        if (externalAuthWindow !== source) {
97
-            logger.warn('Ignored message not coming '
98
-                + 'from external authnetication window');
99
-
100
-            return;
101
-        }
102
-
103
-        let jwt;
104
-
105
-        if (data && (jwt = data.jwtToken)) {
106
-            logger.info('Received JSON Web Token (JWT):', jwt);
107
-
108
-            APP.store.dispatch(setJWT(jwt));
109
-
110
-            const roomName = room.getName();
111
-
112
-            openConnection({
113
-                retry: false,
114
-                roomName
115
-            }).then(connection => {
116
-                // Start new connection
117
-                const newRoom = connection.initJitsiConference(
118
-                    roomName, APP.conference._getConferenceOptions());
119
-
120
-                // Authenticate from the new connection to get
121
-                // the session-ID from the focus, which will then be used
122
-                // to upgrade current connection's user role
123
-
124
-                newRoom.room.moderator.authenticate()
125
-                .then(() => {
126
-                    connection.disconnect();
127
-
128
-                    // At this point we'll have session-ID stored in
129
-                    // the settings. It will be used in the call below
130
-                    // to upgrade user's role
131
-                    room.room.moderator.authenticate()
132
-                        .then(() => {
133
-                            logger.info('User role upgrade done !');
134
-                            // eslint-disable-line no-use-before-define
135
-                            unregister();
136
-                        })
137
-                        .catch((err, errCode) => {
138
-                            logger.error('Authentication failed: ',
139
-                                err, errCode);
140
-                            unregister();
141
-                        });
142
-                })
143
-                .catch((error, code) => {
144
-                    unregister();
145
-                    connection.disconnect();
146
-                    logger.error(
147
-                        'Authentication failed on the new connection',
148
-                        error, code);
149
-                });
150
-            }, err => {
151
-                unregister();
152
-                logger.error('Failed to open new connection', err);
153
-            });
154
-        }
155
-    }
156
-
157
-    /**
158
-     *
159
-     */
160
-    function unregister() {
161
-        window.removeEventListener('message', listener);
162
-    }
163
-
164
-    if (window.addEventListener) {
165
-        window.addEventListener('message', listener, false);
166
-    }
167
-}
168
-
169
-/**
170
- * Authenticate for the conference.
171
- * Uses external service for auth if conference supports that.
172
- * @param {JitsiConference} room
173
- * @param {string} [lockPassword] password to use if the conference is locked
174
- */
175
-function authenticate(room, lockPassword) {
176
-    const config = APP.store.getState()['features/base/config'];
177
-
178
-    if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
179
-        doExternalAuth(room, lockPassword);
180
-    } else {
181
-        APP.store.dispatch(openLoginDialog());
182
-    }
183
-}
184
-
185
-/**
186
- * Notify user that authentication is required to create the conference.
187
- * @param {JitsiConference} room
188
- * @param {string} [lockPassword] password to use if the conference is locked
189
- */
190
-function requireAuth(room, lockPassword) {
191
-    if (isDialogOpen(APP.store, WaitForOwnerDialog) || isDialogOpen(APP.store, LoginDialog)) {
192
-        return;
193
-    }
194
-
195
-    APP.store.dispatch(
196
-        openAuthDialog(
197
-        room.getName(), authenticate.bind(null, room, lockPassword))
198
-    );
199
-}
200
-
201
-/**
202
- * De-authenticate local user.
203
- *
204
- * @param {JitsiConference} room
205
- * @param {string} [lockPassword] password to use if the conference is locked
206
- * @returns {Promise}
207
- */
208
-function logout(room) {
209
-    return new Promise(resolve => {
210
-        room.room.moderator.logout(resolve);
211
-    }).then(url => {
212
-        // de-authenticate conference on the fly
213
-        if (room.isJoined()) {
214
-            const replaceParticipant = getReplaceParticipant(APP.store.getState());
215
-
216
-            room.join(null, replaceParticipant);
217
-        }
218
-
219
-        return url;
220
-    });
221
-}
222
-
223
-export default {
224
-    authenticate,
225
-    logout,
226
-    requireAuth
227
-};

+ 0
- 28
modules/UI/authentication/LoginDialog.js Ver arquivo

1
-/* global APP */
2
-
3
-export default {
4
-
5
-    /**
6
-     * Show notification that external auth is required (using provided url).
7
-     * @param {string} url - URL to use for external auth.
8
-     * @param {function} callback - callback to invoke when auth popup is closed.
9
-     * @returns auth dialog
10
-     */
11
-    showExternalAuthDialog(url, callback) {
12
-        const dialog = APP.UI.messageHandler.openCenteredPopup(
13
-            url, 910, 660,
14
-
15
-            // On closed
16
-            callback
17
-        );
18
-
19
-        if (!dialog) {
20
-            APP.UI.messageHandler.showWarning({
21
-                descriptionKey: 'dialog.popupError',
22
-                titleKey: 'dialog.popupErrorTitle'
23
-            });
24
-        }
25
-
26
-        return dialog;
27
-    }
28
-};

+ 0
- 61
modules/UI/util/MessageHandler.js Ver arquivo

1
-/* global APP */
2
-
3
-import { showErrorNotification, showWarningNotification } from '../../../react/features/notifications/actions';
4
-import { NOTIFICATION_TIMEOUT_TYPE } from '../../../react/features/notifications/constants';
5
-
6
-const messageHandler = {
7
-    /**
8
-     * Opens new popup window for given <tt>url</tt> centered over current
9
-     * window.
10
-     *
11
-     * @param url the URL to be displayed in the popup window
12
-     * @param w the width of the popup window
13
-     * @param h the height of the popup window
14
-     * @param onPopupClosed optional callback function called when popup window
15
-     *        has been closed.
16
-     *
17
-     * @returns {object} popup window object if opened successfully or undefined
18
-     *          in case we failed to open it(popup blocked)
19
-     */
20
-    // eslint-disable-next-line max-params
21
-    openCenteredPopup(url, w, h, onPopupClosed) {
22
-        const l = window.screenX + (window.innerWidth / 2) - (w / 2);
23
-        const t = window.screenY + (window.innerHeight / 2) - (h / 2);
24
-        const popup = window.open(
25
-            url, '_blank',
26
-            String(`top=${t}, left=${l}, width=${w}, height=${h}`));
27
-
28
-        if (popup && onPopupClosed) {
29
-            const pollTimer = window.setInterval(() => {
30
-                if (popup.closed !== false) {
31
-                    window.clearInterval(pollTimer);
32
-                    onPopupClosed();
33
-                }
34
-            }, 200);
35
-        }
36
-
37
-        return popup;
38
-    },
39
-
40
-    /**
41
-     * Shows an error dialog to the user.
42
-     *
43
-     * @param {object} props - The properties to pass to the
44
-     * showErrorNotification action.
45
-     */
46
-    showError(props) {
47
-        APP.store.dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG));
48
-    },
49
-
50
-    /**
51
-     * Shows a warning dialog to the user.
52
-     *
53
-     * @param {object} props - The properties to pass to the
54
-     * showWarningNotification action.
55
-     */
56
-    showWarning(props) {
57
-        APP.store.dispatch(showWarningNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG));
58
-    }
59
-};
60
-
61
-export default messageHandler;

+ 0
- 12
modules/UI/util/UIUtil.js Ver arquivo

31
         return result;
31
         return result;
32
     },
32
     },
33
 
33
 
34
-    /**
35
-     * Redirects to a given URL.
36
-     *
37
-     * @param {string} url - The redirect URL.
38
-     * NOTE: Currently used to redirect to 3rd party location for
39
-     * authentication. In most cases redirectWithStoredParams action must be
40
-     * used instead of this method in order to preserve current URL params.
41
-     */
42
-    redirect(url) {
43
-        window.location.href = url;
44
-    },
45
-
46
     /**
34
     /**
47
      * Indicates if we're currently in full screen mode.
35
      * Indicates if we're currently in full screen mode.
48
      *
36
      *

+ 1
- 0
react/features/app/middlewares.any.ts Ver arquivo

1
 import '../analytics/middleware';
1
 import '../analytics/middleware';
2
+import '../authentication/middleware';
2
 import '../av-moderation/middleware';
3
 import '../av-moderation/middleware';
3
 import '../base/conference/middleware';
4
 import '../base/conference/middleware';
4
 import '../base/config/middleware';
5
 import '../base/config/middleware';

+ 0
- 1
react/features/app/middlewares.native.ts Ver arquivo

1
-import '../authentication/middleware';
2
 import '../dynamic-branding/middleware';
1
 import '../dynamic-branding/middleware';
3
 import '../gifs/middleware';
2
 import '../gifs/middleware';
4
 import '../mobile/audio-mode/middleware';
3
 import '../mobile/audio-mode/middleware';

+ 1
- 1
react/features/app/middlewares.web.ts Ver arquivo

1
-import '../authentication/middleware';
1
+import '../base/connection/middleware';
2
 import '../base/i18n/middleware';
2
 import '../base/i18n/middleware';
3
 import '../base/devices/middleware';
3
 import '../base/devices/middleware';
4
 import '../base/media/middleware';
4
 import '../base/media/middleware';

+ 18
- 0
react/features/authentication/actionTypes.ts Ver arquivo

8
  */
8
  */
9
 export const CANCEL_LOGIN = 'CANCEL_LOGIN';
9
 export const CANCEL_LOGIN = 'CANCEL_LOGIN';
10
 
10
 
11
+/**
12
+ * The type of (redux) action which signals to login.
13
+ *
14
+ * {
15
+ *     type: LOGOUT
16
+ * }
17
+ */
18
+export const LOGIN = 'LOGIN';
19
+
20
+/**
21
+ * The type of (redux) action which signals to logout.
22
+ *
23
+ * {
24
+ *     type: LOGOUT
25
+ * }
26
+ */
27
+export const LOGOUT = 'LOGOUT';
28
+
11
 /**
29
 /**
12
  * The type of (redux) action which signals that the cyclic operation of waiting
30
  * The type of (redux) action which signals that the cyclic operation of waiting
13
  * for conference owner has been aborted.
31
  * for conference owner has been aborted.

+ 11
- 1
react/features/authentication/actions.any.ts Ver arquivo

1
 import { IStore } from '../app/types';
1
 import { IStore } from '../app/types';
2
 import { checkIfCanJoin } from '../base/conference/actions';
2
 import { checkIfCanJoin } from '../base/conference/actions';
3
 import { IJitsiConference } from '../base/conference/reducer';
3
 import { IJitsiConference } from '../base/conference/reducer';
4
-import { openDialog } from '../base/dialog/actions';
4
+import { hideDialog, openDialog } from '../base/dialog/actions';
5
 
5
 
6
 import {
6
 import {
7
     STOP_WAIT_FOR_OWNER,
7
     STOP_WAIT_FOR_OWNER,
126
     };
126
     };
127
 }
127
 }
128
 
128
 
129
+/**
130
+ * Hides an authentication dialog where the local participant
131
+ * should authenticate.
132
+ *
133
+ * @returns {Function}.
134
+ */
135
+export function hideLoginDialog() {
136
+    return hideDialog(LoginDialog);
137
+}
138
+
129
 /**
139
 /**
130
  * Opens {@link WaitForOnwerDialog}.
140
  * Opens {@link WaitForOnwerDialog}.
131
  *
141
  *

+ 9
- 2
react/features/authentication/actions.native.ts Ver arquivo

1
-import { appNavigate } from '../app/actions';
1
+import { appNavigate } from '../app/actions.native';
2
 import { IStore } from '../app/types';
2
 import { IStore } from '../app/types';
3
 import { conferenceLeft } from '../base/conference/actions';
3
 import { conferenceLeft } from '../base/conference/actions';
4
 import { connectionFailed } from '../base/connection/actions.native';
4
 import { connectionFailed } from '../base/connection/actions.native';
61
     };
61
     };
62
 }
62
 }
63
 
63
 
64
-
64
+/** .
65
+ * Redirect to the default location (e.g. Welcome page).
66
+ *
67
+ * @returns {Function}
68
+ */
69
+export function redirectToDefaultLocation() {
70
+    return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
71
+}

+ 28
- 23
react/features/authentication/actions.web.ts Ver arquivo

1
 import { maybeRedirectToWelcomePage } from '../app/actions.web';
1
 import { maybeRedirectToWelcomePage } from '../app/actions.web';
2
 import { IStore } from '../app/types';
2
 import { IStore } from '../app/types';
3
-import { hideDialog, openDialog } from '../base/dialog/actions';
4
 
3
 
5
 import {
4
 import {
6
-    CANCEL_LOGIN
5
+    CANCEL_LOGIN,
6
+    LOGIN,
7
+    LOGOUT
7
 } from './actionTypes';
8
 } from './actionTypes';
8
-import LoginDialog from './components/web/LoginDialog';
9
-import WaitForOwnerDialog from './components/web/WaitForOwnerDialog';
10
 
9
 
11
 export * from './actions.any';
10
 export * from './actions.any';
12
 
11
 
35
     };
34
     };
36
 }
35
 }
37
 
36
 
38
-/**
39
- * Hides a authentication dialog where the local participant
40
- * should authenticate.
37
+/** .
38
+ * Redirect to the default location (e.g. Welcome page).
41
  *
39
  *
42
- * @returns {Function}.
40
+ * @returns {Function}
43
  */
41
  */
44
-export function hideLoginDialog() {
45
-    return hideDialog(LoginDialog);
42
+export function redirectToDefaultLocation() {
43
+    return (dispatch: IStore['dispatch']) => dispatch(maybeRedirectToWelcomePage());
46
 }
44
 }
47
 
45
 
48
 /**
46
 /**
49
- * Shows a notification dialog that authentication is required to create the.
50
- * Conference.
51
- * This is used for external auth.
47
+ * Login.
52
  *
48
  *
53
- * @param {string} room - The room name.
54
- * @param {Function} onAuthNow - The function to be invoked when external authentication.
55
- *
56
- * @returns {Function}.
49
+ * @returns {{
50
+ *     type: LOGIN
51
+ * }}
57
  */
52
  */
58
-export function openAuthDialog(room: String, onAuthNow?: Function) {
59
-    return openDialog(WaitForOwnerDialog, {
60
-        room,
61
-        onAuthNow
62
-    });
53
+export function login() {
54
+    return {
55
+        type: LOGIN
56
+    };
63
 }
57
 }
64
 
58
 
65
-
59
+/**
60
+ * Logout.
61
+ *
62
+ * @returns {{
63
+ *     type: LOGOUT
64
+ * }}
65
+ */
66
+export function logout() {
67
+    return {
68
+        type: LOGOUT
69
+    };
70
+}

+ 5
- 32
react/features/authentication/components/web/LoginDialog.tsx Ver arquivo

2
 import { WithTranslation } from 'react-i18next';
2
 import { WithTranslation } from 'react-i18next';
3
 import { connect as reduxConnect } from 'react-redux';
3
 import { connect as reduxConnect } from 'react-redux';
4
 
4
 
5
-// @ts-expect-error
6
-import { connect } from '../../../../../connection';
7
 import { IReduxState, IStore } from '../../../app/types';
5
 import { IReduxState, IStore } from '../../../app/types';
8
 import { IJitsiConference } from '../../../base/conference/reducer';
6
 import { IJitsiConference } from '../../../base/conference/reducer';
9
 import { IConfig } from '../../../base/config/configType';
7
 import { IConfig } from '../../../base/config/configType';
8
+import { connect } from '../../../base/connection/actions.web';
10
 import { toJid } from '../../../base/connection/functions';
9
 import { toJid } from '../../../base/connection/functions';
11
 import { translate, translateToHTML } from '../../../base/i18n/functions';
10
 import { translate, translateToHTML } from '../../../base/i18n/functions';
12
 import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
11
 import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
54
      */
53
      */
55
     dispatch: IStore['dispatch'];
54
     dispatch: IStore['dispatch'];
56
 
55
 
57
-    /**
58
-     * Invoked when username and password are submitted.
59
-     */
60
-    onSuccess: Function;
61
-
62
     /**
56
     /**
63
      * Conference room name.
57
      * Conference room name.
64
      */
58
      */
70
  */
64
  */
71
 interface IState {
65
 interface IState {
72
 
66
 
73
-    /**
74
-     * Authentication process starts before joining the conference room.
75
-     */
76
-    loginStarted: boolean;
77
-
78
     /**
67
     /**
79
      * The user entered password for the conference.
68
      * The user entered password for the conference.
80
      */
69
      */
102
 
91
 
103
         this.state = {
92
         this.state = {
104
             username: '',
93
             username: '',
105
-            password: '',
106
-            loginStarted: false
94
+            password: ''
107
         };
95
         };
108
 
96
 
109
         this._onCancelLogin = this._onCancelLogin.bind(this);
97
         this._onCancelLogin = this._onCancelLogin.bind(this);
135
         const {
123
         const {
136
             _conference: conference,
124
             _conference: conference,
137
             _configHosts: configHosts,
125
             _configHosts: configHosts,
138
-            roomName,
139
-            onSuccess,
140
             dispatch
126
             dispatch
141
         } = this.props;
127
         } = this.props;
142
         const { password, username } = this.state;
128
         const { password, username } = this.state;
148
         if (conference) {
134
         if (conference) {
149
             dispatch(authenticateAndUpgradeRole(jid, password, conference));
135
             dispatch(authenticateAndUpgradeRole(jid, password, conference));
150
         } else {
136
         } else {
151
-            this.setState({
152
-                loginStarted: true
153
-            });
154
-
155
-            connect(jid, password, roomName)
156
-                .then((connection: any) => {
157
-                    onSuccess?.(connection);
158
-                })
159
-                .catch(() => {
160
-                    this.setState({
161
-                        loginStarted: false
162
-                    });
163
-                });
137
+            dispatch(connect(jid, password));
164
         }
138
         }
165
     }
139
     }
166
 
140
 
249
             _connecting: connecting,
223
             _connecting: connecting,
250
             t
224
             t
251
         } = this.props;
225
         } = this.props;
252
-        const { password, loginStarted, username } = this.state;
226
+        const { password, username } = this.state;
253
 
227
 
254
         return (
228
         return (
255
             <Dialog
229
             <Dialog
258
                 hideCloseButton = { true }
232
                 hideCloseButton = { true }
259
                 ok = {{
233
                 ok = {{
260
                     disabled: connecting
234
                     disabled: connecting
261
-                        || loginStarted
262
                         || !password
235
                         || !password
263
                         || !username,
236
                         || !username,
264
                     translationKey: 'dialog.login'
237
                     translationKey: 'dialog.login'
315
     return {
288
     return {
316
         _conference: authRequired || conference,
289
         _conference: authRequired || conference,
317
         _configHosts: configHosts,
290
         _configHosts: configHosts,
318
-        _connecting: connecting || thenableWithCancel,
291
+        _connecting: Boolean(connecting) || Boolean(thenableWithCancel),
319
         _error: connectionError || authenticateAndUpgradeRoleError,
292
         _error: connectionError || authenticateAndUpgradeRoleError,
320
         _progress: progress
293
         _progress: progress
321
     };
294
     };

+ 2
- 9
react/features/authentication/components/web/WaitForOwnerDialog.tsx Ver arquivo

5
 import { IStore } from '../../../app/types';
5
 import { IStore } from '../../../app/types';
6
 import { translate } from '../../../base/i18n/functions';
6
 import { translate } from '../../../base/i18n/functions';
7
 import Dialog from '../../../base/ui/components/web/Dialog';
7
 import Dialog from '../../../base/ui/components/web/Dialog';
8
-import { cancelWaitForOwner } from '../../actions.web';
8
+import { cancelWaitForOwner, login } from '../../actions.web';
9
 
9
 
10
 /**
10
 /**
11
  * The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
11
  * The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
16
      * Redux store dispatch method.
16
      * Redux store dispatch method.
17
      */
17
      */
18
     dispatch: IStore['dispatch'];
18
     dispatch: IStore['dispatch'];
19
-
20
-    /**
21
-     * Function to be invoked after click.
22
-     */
23
-    onAuthNow?: Function;
24
 }
19
 }
25
 
20
 
26
 /**
21
 /**
61
      * @returns {void}
56
      * @returns {void}
62
      */
57
      */
63
     _onIAmHost() {
58
     _onIAmHost() {
64
-        const { onAuthNow } = this.props;
65
-
66
-        onAuthNow?.();
59
+        this.props.dispatch(login());
67
     }
60
     }
68
 
61
 
69
     /**
62
     /**

react/features/authentication/middleware.native.ts → react/features/authentication/middleware.ts Ver arquivo

1
-import { appNavigate } from '../app/actions.native';
2
 import { IStore } from '../app/types';
1
 import { IStore } from '../app/types';
3
 import {
2
 import {
4
     CONFERENCE_FAILED,
3
     CONFERENCE_FAILED,
6
     CONFERENCE_LEFT
5
     CONFERENCE_LEFT
7
 } from '../base/conference/actionTypes';
6
 } from '../base/conference/actionTypes';
8
 import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
7
 import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
8
+import { hangup } from '../base/connection/actions';
9
 import { hideDialog } from '../base/dialog/actions';
9
 import { hideDialog } from '../base/dialog/actions';
10
 import { isDialogOpen } from '../base/dialog/functions';
10
 import { isDialogOpen } from '../base/dialog/functions';
11
 import {
11
 import {
13
     JitsiConnectionErrors
13
     JitsiConnectionErrors
14
 } from '../base/lib-jitsi-meet';
14
 } from '../base/lib-jitsi-meet';
15
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
15
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
16
+import { getBackendSafeRoomName } from '../base/util/uri';
17
+import { showErrorNotification } from '../notifications/actions';
18
+import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
19
+import { openLogoutDialog } from '../settings/actions';
16
 
20
 
17
 import {
21
 import {
18
     CANCEL_LOGIN,
22
     CANCEL_LOGIN,
23
+    LOGIN,
24
+    LOGOUT,
19
     STOP_WAIT_FOR_OWNER,
25
     STOP_WAIT_FOR_OWNER,
20
     UPGRADE_ROLE_FINISHED,
26
     UPGRADE_ROLE_FINISHED,
21
     WAIT_FOR_OWNER
27
     WAIT_FOR_OWNER
22
 } from './actionTypes';
28
 } from './actionTypes';
23
 import {
29
 import {
30
+    hideLoginDialog,
24
     openLoginDialog,
31
     openLoginDialog,
25
     openWaitForOwnerDialog,
32
     openWaitForOwnerDialog,
33
+    redirectToDefaultLocation,
26
     stopWaitForOwner,
34
     stopWaitForOwner,
27
-    waitForOwner } from './actions.native';
35
+    waitForOwner } from './actions';
28
 import { LoginDialog, WaitForOwnerDialog } from './components';
36
 import { LoginDialog, WaitForOwnerDialog } from './components';
37
+import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
29
 
38
 
30
 /**
39
 /**
31
  * Middleware that captures connection or conference failed errors and controls
40
  * Middleware that captures connection or conference failed errors and controls
40
     switch (action.type) {
49
     switch (action.type) {
41
     case CANCEL_LOGIN: {
50
     case CANCEL_LOGIN: {
42
         const { dispatch, getState } = store;
51
         const { dispatch, getState } = store;
43
-        const { thenableWithCancel } = getState()['features/authentication'];
52
+        const state = getState();
53
+        const { thenableWithCancel } = state['features/authentication'];
44
 
54
 
45
         thenableWithCancel?.cancel();
55
         thenableWithCancel?.cancel();
46
 
56
 
57
                 return result;
67
                 return result;
58
             }
68
             }
59
 
69
 
60
-            // Go back to the app's entry point.
61
-            _hideLoginDialog(store);
70
+            dispatch(hideLoginDialog());
62
 
71
 
63
-            const state = getState();
64
             const { authRequired, conference } = state['features/base/conference'];
72
             const { authRequired, conference } = state['features/base/conference'];
65
             const { passwordRequired } = state['features/base/connection'];
73
             const { passwordRequired } = state['features/base/connection'];
66
 
74
 
68
             // NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
76
             // NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
69
             // connection auth error.
77
             // connection auth error.
70
             if ((passwordRequired || authRequired) && !conference) {
78
             if ((passwordRequired || authRequired) && !conference) {
71
-                dispatch(appNavigate(undefined));
79
+                dispatch(redirectToDefaultLocation());
72
             }
80
             }
73
         }
81
         }
74
         break;
82
         break;
100
         if (_isWaitingForOwner(store)) {
108
         if (_isWaitingForOwner(store)) {
101
             store.dispatch(stopWaitForOwner());
109
             store.dispatch(stopWaitForOwner());
102
         }
110
         }
103
-        _hideLoginDialog(store);
111
+        store.dispatch(hideLoginDialog());
104
         break;
112
         break;
105
 
113
 
106
     case CONFERENCE_LEFT:
114
     case CONFERENCE_LEFT:
108
         break;
116
         break;
109
 
117
 
110
     case CONNECTION_ESTABLISHED:
118
     case CONNECTION_ESTABLISHED:
111
-        _hideLoginDialog(store);
119
+        store.dispatch(hideLoginDialog());
112
         break;
120
         break;
113
 
121
 
114
     case CONNECTION_FAILED: {
122
     case CONNECTION_FAILED: {
115
         const { error } = action;
123
         const { error } = action;
124
+        const state = store.getState();
125
+        const { jwt } = state['features/base/jwt'];
116
 
126
 
117
         if (error
127
         if (error
118
                 && error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
128
                 && error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
119
-                && typeof error.recoverable === 'undefined') {
129
+                && typeof error.recoverable === 'undefined'
130
+                && !jwt) {
120
             error.recoverable = true;
131
             error.recoverable = true;
121
-            store.dispatch(openLoginDialog());
132
+
133
+            _handleLogin(store);
134
+        }
135
+
136
+        break;
137
+    }
138
+
139
+    case LOGIN: {
140
+        _handleLogin(store);
141
+
142
+        break;
143
+    }
144
+
145
+    case LOGOUT: {
146
+        const { conference } = store.getState()['features/base/conference'];
147
+
148
+        if (!conference) {
149
+            break;
122
         }
150
         }
151
+
152
+        store.dispatch(openLogoutDialog(() =>
153
+            conference.room.moderator.logout(() => store.dispatch(hangup(true)))
154
+        ));
155
+
123
         break;
156
         break;
124
     }
157
     }
125
 
158
 
132
         const { error, progress } = action;
165
         const { error, progress } = action;
133
 
166
 
134
         if (!error && progress === 1) {
167
         if (!error && progress === 1) {
135
-            _hideLoginDialog(store);
168
+            store.dispatch(hideLoginDialog());
136
         }
169
         }
137
         break;
170
         break;
138
     }
171
     }
144
 
177
 
145
         action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
178
         action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
146
 
179
 
147
-        // The WAIT_FOR_OWNER action is cyclic and we don't want to hide the
180
+        // The WAIT_FOR_OWNER action is cyclic, and we don't want to hide the
148
         // login dialog every few seconds.
181
         // login dialog every few seconds.
149
         isDialogOpen(store, LoginDialog)
182
         isDialogOpen(store, LoginDialog)
150
             || store.dispatch(openWaitForOwnerDialog());
183
             || store.dispatch(openWaitForOwnerDialog());
162
  * @param {Object} store - The redux store.
195
  * @param {Object} store - The redux store.
163
  * @returns {void}
196
  * @returns {void}
164
  */
197
  */
165
-function _clearExistingWaitForOwnerTimeout(
166
-        { getState }: IStore) {
198
+function _clearExistingWaitForOwnerTimeout({ getState }: IStore) {
167
     const { waitForOwnerTimeoutID } = getState()['features/authentication'];
199
     const { waitForOwnerTimeoutID } = getState()['features/authentication'];
168
 
200
 
169
     waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
201
     waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
170
 }
202
 }
171
 
203
 
172
-/**
173
- * Hides {@link LoginDialog} if it's currently displayed.
174
- *
175
- * @param {Object} store - The redux store.
176
- * @returns {void}
177
- */
178
-function _hideLoginDialog({ dispatch }: IStore) {
179
-    dispatch(hideDialog(LoginDialog));
180
-}
181
 
204
 
182
 /**
205
 /**
183
  * Checks if the cyclic "wait for conference owner" task is currently scheduled.
206
  * Checks if the cyclic "wait for conference owner" task is currently scheduled.
188
 function _isWaitingForOwner({ getState }: IStore) {
211
 function _isWaitingForOwner({ getState }: IStore) {
189
     return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
212
     return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
190
 }
213
 }
214
+
215
+/**
216
+ * Handles login challenge. Opens login dialog or redirects to token auth URL.
217
+ *
218
+ * @param {Store} store - The redux store in which the specified {@code action}
219
+ * is being dispatched.
220
+ * @returns {void}
221
+ */
222
+function _handleLogin({ dispatch, getState }: IStore) {
223
+    const state = getState();
224
+    const config = state['features/base/config'];
225
+    const room = getBackendSafeRoomName(state['features/base/conference'].room);
226
+
227
+    if (isTokenAuthEnabled(config)) {
228
+        if (typeof APP === 'undefined') {
229
+            dispatch(showErrorNotification({
230
+                descriptionKey: 'dialog.tokenAuthUnsupported',
231
+                titleKey: 'dialog.tokenAuthFailedTitle'
232
+            }, NOTIFICATION_TIMEOUT_TYPE.LONG));
233
+
234
+            dispatch(redirectToDefaultLocation());
235
+
236
+            return;
237
+        }
238
+
239
+        // FIXME: This method will not preserve the other URL params that were originally passed.
240
+        // redirectToTokenAuthService
241
+        window.location.href = getTokenAuthUrl(config)(room, false);
242
+    } else {
243
+        dispatch(openLoginDialog());
244
+    }
245
+}

+ 0
- 152
react/features/authentication/middleware.web.ts Ver arquivo

1
-import { maybeRedirectToWelcomePage } from '../app/actions.web';
2
-import { IStore } from '../app/types';
3
-import {
4
-    CONFERENCE_FAILED,
5
-    CONFERENCE_JOINED,
6
-    CONFERENCE_LEFT
7
-} from '../base/conference/actionTypes';
8
-import { CONNECTION_ESTABLISHED } from '../base/connection/actionTypes';
9
-import { hideDialog } from '../base/dialog/actions';
10
-import { isDialogOpen } from '../base/dialog/functions';
11
-import {
12
-    JitsiConferenceErrors
13
-} from '../base/lib-jitsi-meet';
14
-import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
15
-
16
-import {
17
-    CANCEL_LOGIN,
18
-    STOP_WAIT_FOR_OWNER,
19
-    UPGRADE_ROLE_FINISHED,
20
-    WAIT_FOR_OWNER
21
-} from './actionTypes';
22
-import {
23
-    hideLoginDialog,
24
-    openWaitForOwnerDialog,
25
-    stopWaitForOwner
26
-} from './actions.web';
27
-import LoginDialog from './components/web/LoginDialog';
28
-import WaitForOwnerDialog from './components/web/WaitForOwnerDialog';
29
-
30
-/**
31
- * Middleware that captures connection or conference failed errors and controls
32
- * {@link WaitForOwnerDialog} and {@link LoginDialog}.
33
- *
34
- * FIXME Some of the complexity was introduced by the lack of dialog stacking.
35
- *
36
- * @param {Store} store - Redux store.
37
- * @returns {Function}
38
- */
39
-MiddlewareRegistry.register(store => next => action => {
40
-    switch (action.type) {
41
-
42
-    case CANCEL_LOGIN: {
43
-        const { dispatch, getState } = store;
44
-
45
-        if (!isDialogOpen(store, WaitForOwnerDialog)) {
46
-            if (_isWaitingForOwner(store)) {
47
-                dispatch(openWaitForOwnerDialog());
48
-
49
-                return next(action);
50
-            }
51
-
52
-            dispatch(hideLoginDialog());
53
-
54
-            const { authRequired, conference } = getState()['features/base/conference'];
55
-            const { passwordRequired } = getState()['features/base/connection'];
56
-
57
-            // Only end the meeting if we are not already inside and trying to upgrade.
58
-            if ((authRequired && !conference) || passwordRequired) {
59
-                dispatch(maybeRedirectToWelcomePage());
60
-            }
61
-        }
62
-        break;
63
-    }
64
-
65
-    case CONFERENCE_FAILED: {
66
-        const { error } = action;
67
-        let recoverable;
68
-
69
-        if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
70
-            if (typeof error.recoverable === 'undefined') {
71
-                error.recoverable = true;
72
-            }
73
-            recoverable = error.recoverable;
74
-        }
75
-        if (recoverable) {
76
-            // we haven't migrated all the code from AuthHandler, and we need for now conference.js to trigger
77
-            // the dialog to pass all required parameters to WaitForOwnerDialog
78
-            // keep it commented, so we do not trigger sending iqs to jicofo twice
79
-            // and showing the broken dialog with no handler
80
-            // store.dispatch(waitForOwner());
81
-        } else {
82
-            store.dispatch(stopWaitForOwner());
83
-        }
84
-        break;
85
-    }
86
-
87
-    case CONFERENCE_JOINED:
88
-        store.dispatch(stopWaitForOwner());
89
-        store.dispatch(hideLoginDialog());
90
-        break;
91
-
92
-    case CONFERENCE_LEFT:
93
-        store.dispatch(stopWaitForOwner());
94
-        break;
95
-
96
-    case CONNECTION_ESTABLISHED:
97
-        store.dispatch(hideLoginDialog());
98
-        break;
99
-
100
-    case STOP_WAIT_FOR_OWNER:
101
-        _clearExistingWaitForOwnerTimeout(store);
102
-        store.dispatch(hideDialog(WaitForOwnerDialog));
103
-        break;
104
-
105
-    case UPGRADE_ROLE_FINISHED: {
106
-        const { error, progress } = action;
107
-
108
-        if (!error && progress === 1) {
109
-            store.dispatch(hideLoginDialog());
110
-        }
111
-        break;
112
-    }
113
-
114
-    case WAIT_FOR_OWNER: {
115
-        _clearExistingWaitForOwnerTimeout(store);
116
-
117
-        const { handler, timeoutMs }: { handler: () => void; timeoutMs: number; } = action;
118
-
119
-        action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
120
-
121
-        isDialogOpen(store, LoginDialog)
122
-            || store.dispatch(openWaitForOwnerDialog());
123
-        break;
124
-    }
125
-    }
126
-
127
-    return next(action);
128
-});
129
-
130
-/**
131
- * Will clear the wait for conference owner timeout handler if any is currently
132
- * set.
133
- *
134
- * @param {Object} store - The redux store.
135
- * @returns {void}
136
- */
137
-function _clearExistingWaitForOwnerTimeout(
138
-        { getState }: IStore) {
139
-    const { waitForOwnerTimeoutID } = getState()['features/authentication'];
140
-
141
-    waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
142
-}
143
-
144
-/**
145
- * Checks if the cyclic "wait for conference owner" task is currently scheduled.
146
- *
147
- * @param {Object} store - The redux store.
148
- * @returns {void}
149
- */
150
-function _isWaitingForOwner({ getState }: IStore) {
151
-    return getState()['features/authentication'].waitForOwnerTimeoutID;
152
-}

+ 9
- 0
react/features/base/conference/actionTypes.ts Ver arquivo

106
  */
106
  */
107
 export const E2E_RTT_CHANGED = 'E2E_RTT_CHANGED'
107
 export const E2E_RTT_CHANGED = 'E2E_RTT_CHANGED'
108
 
108
 
109
+/**
110
+ * The type of (redux) action which signals that a conference will be initialized.
111
+ *
112
+ * {
113
+ *     type: CONFERENCE_WILL_INIT
114
+ * }
115
+ */
116
+export const CONFERENCE_WILL_INIT = 'CONFERENCE_WILL_INIT';
117
+
109
 /**
118
 /**
110
  * The type of (redux) action which signals that a specific conference will be
119
  * The type of (redux) action which signals that a specific conference will be
111
  * joined.
120
  * joined.

+ 16
- 12
react/features/base/conference/actions.ts Ver arquivo

1
 import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
1
 import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
2
 import { sendAnalytics } from '../../analytics/functions';
2
 import { sendAnalytics } from '../../analytics/functions';
3
-import { appNavigate } from '../../app/actions';
4
 import { IReduxState, IStore } from '../../app/types';
3
 import { IReduxState, IStore } from '../../app/types';
5
 import { endpointMessageReceived } from '../../subtitles/actions.any';
4
 import { endpointMessageReceived } from '../../subtitles/actions.any';
6
 import { iAmVisitor } from '../../visitors/functions';
5
 import { iAmVisitor } from '../../visitors/functions';
7
 import { getReplaceParticipant } from '../config/functions';
6
 import { getReplaceParticipant } from '../config/functions';
8
-import { disconnect } from '../connection/actions';
7
+import { hangup } from '../connection/actions';
9
 import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
8
 import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
10
 import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
9
 import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
11
 import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
10
 import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
41
     CONFERENCE_SUBJECT_CHANGED,
40
     CONFERENCE_SUBJECT_CHANGED,
42
     CONFERENCE_TIMESTAMP_CHANGED,
41
     CONFERENCE_TIMESTAMP_CHANGED,
43
     CONFERENCE_UNIQUE_ID_SET,
42
     CONFERENCE_UNIQUE_ID_SET,
43
+    CONFERENCE_WILL_INIT,
44
     CONFERENCE_WILL_JOIN,
44
     CONFERENCE_WILL_JOIN,
45
     CONFERENCE_WILL_LEAVE,
45
     CONFERENCE_WILL_LEAVE,
46
     DATA_CHANNEL_CLOSED,
46
     DATA_CHANNEL_CLOSED,
463
     };
463
     };
464
 }
464
 }
465
 
465
 
466
+/**
467
+ * Signals the intention of the application to have a conference initialized.
468
+ *
469
+ * @returns {{
470
+ *     type: CONFERENCE_WILL_INIT
471
+ * }}
472
+ */
473
+export function conferenceWillInit() {
474
+    return {
475
+        type: CONFERENCE_WILL_INIT
476
+    };
477
+}
478
+
466
 /**
479
 /**
467
  * Signals the intention of the application to have the local participant
480
  * Signals the intention of the application to have the local participant
468
  * join the specified conference.
481
  * join the specified conference.
647
  * @returns {Function}
660
  * @returns {Function}
648
  */
661
  */
649
 export function leaveConference() {
662
 export function leaveConference() {
650
-    return async (dispatch: IStore['dispatch']) => {
651
-
652
-        // FIXME: these should be unified.
653
-        if (navigator.product === 'ReactNative') {
654
-            dispatch(appNavigate(undefined));
655
-        } else {
656
-            dispatch(disconnect(true));
657
-        }
658
-    };
663
+    return async (dispatch: IStore['dispatch']) => dispatch(hangup(true));
659
 }
664
 }
660
 
665
 
661
-
662
 /**
666
 /**
663
  * Signals that the lock state of a specific JitsiConference changed.
667
  * Signals that the lock state of a specific JitsiConference changed.
664
  *
668
  *

+ 86
- 84
react/features/base/conference/middleware.any.ts Ver arquivo

49
 } from './actionTypes';
49
 } from './actionTypes';
50
 import {
50
 import {
51
     conferenceFailed,
51
     conferenceFailed,
52
+    conferenceWillInit,
52
     conferenceWillLeave,
53
     conferenceWillLeave,
53
     createConference,
54
     createConference,
54
     leaveConference,
55
     leaveConference,
144
 function _conferenceFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
145
 function _conferenceFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
145
     const { conference, error } = action;
146
     const { conference, error } = action;
146
 
147
 
147
-    if (error.name === JitsiConferenceErrors.REDIRECTED) {
148
-        if (typeof error.recoverable === 'undefined') {
149
-            error.recoverable = true;
150
-        }
151
-    }
152
-
153
     const result = next(action);
148
     const result = next(action);
154
     const { enableForcedReload } = getState()['features/base/config'];
149
     const { enableForcedReload } = getState()['features/base/config'];
155
 
150
 
197
         break;
192
         break;
198
     }
193
     }
199
     case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
194
     case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
200
-        if (typeof APP === 'undefined') {
201
-            // in case of max users(it can be from a visitor node), let's restore
202
-            // oldConfig if any as we will be back to the main prosody
203
-            const newConfig = restoreConferenceOptions(getState);
204
-
205
-            if (newConfig) {
206
-                dispatch(overwriteConfig(newConfig)) // @ts-ignore
207
-                    .then(dispatch(conferenceWillLeave(conference)))
208
-                    .then(conference.leave())
209
-                    .then(dispatch(disconnect()))
210
-                    .then(dispatch(connect()));
211
-            }
195
+        dispatch(showErrorNotification({
196
+            hideErrorSupportLink: true,
197
+            descriptionKey: 'dialog.maxUsersLimitReached',
198
+            titleKey: 'dialog.maxUsersLimitReachedTitle'
199
+        }, NOTIFICATION_TIMEOUT_TYPE.LONG));
200
+
201
+        // In case of max users(it can be from a visitor node), let's restore
202
+        // oldConfig if any as we will be back to the main prosody.
203
+        const newConfig = restoreConferenceOptions(getState);
204
+
205
+        if (newConfig) {
206
+            dispatch(overwriteConfig(newConfig)) // @ts-ignore
207
+                .then(dispatch(conferenceWillLeave(conference)))
208
+                .then(conference.leave())
209
+                .then(dispatch(disconnect()))
210
+                .then(dispatch(connect()));
212
         }
211
         }
213
 
212
 
214
         break;
213
         break;
216
     case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
215
     case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
217
         sendAnalytics(createOfferAnswerFailedEvent());
216
         sendAnalytics(createOfferAnswerFailedEvent());
218
         break;
217
         break;
218
+
219
     case JitsiConferenceErrors.REDIRECTED: {
219
     case JitsiConferenceErrors.REDIRECTED: {
220
-        // once conference.js is gone this can be removed and both
221
-        // redirect logics to be merged
222
-        if (typeof APP === 'undefined') {
223
-            const newConfig = getVisitorOptions(getState, error.params);
224
-
225
-            if (!newConfig) {
226
-                logger.warn('Not redirected missing params');
227
-                break;
228
-            }
220
+        const newConfig = getVisitorOptions(getState, error.params);
229
 
221
 
230
-            const [ vnode ] = error.params;
222
+        if (!newConfig) {
223
+            logger.warn('Not redirected missing params');
224
+            break;
225
+        }
231
 
226
 
232
-            dispatch(overwriteConfig(newConfig)) // @ts-ignore
233
-                .then(dispatch(conferenceWillLeave(conference)))
234
-                .then(conference.leave())
235
-                .then(dispatch(disconnect()))
236
-                .then(dispatch(setIAmVisitor(Boolean(vnode))))
227
+        const [ vnode ] = error.params;
237
 
228
 
238
-                // we do not clear local tracks on error, so we need to manually clear them
239
-                .then(dispatch(destroyLocalTracks()))
240
-                .then(dispatch(connect()));
241
-        }
229
+        dispatch(overwriteConfig(newConfig)) // @ts-ignore
230
+            .then(dispatch(conferenceWillLeave(conference)))
231
+            .then(dispatch(disconnect()))
232
+            .then(dispatch(setIAmVisitor(Boolean(vnode))))
233
+
234
+            // we do not clear local tracks on error, so we need to manually clear them
235
+            .then(dispatch(destroyLocalTracks()))
236
+            .then(() => {
237
+                dispatch(conferenceWillInit());
238
+            })
239
+            .then(dispatch(connect()));
242
         break;
240
         break;
243
     }
241
     }
244
     }
242
     }
245
 
243
 
246
-    if (typeof APP === 'undefined') {
247
-        !error.recoverable
248
-        && conference
249
-        && conference.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
250
-            // Even though we don't care too much about the failure, it may be
251
-            // good to know that it happen, so log it (on the info level).
252
-            logger.info('JitsiConference.leave() rejected with:', reason);
253
-        });
254
-    } else {
255
-        // FIXME: Workaround for the web version. Currently, the creation of the
256
-        // conference is handled by /conference.js and appropriate failure handlers
257
-        // are set there.
244
+    !error.recoverable
245
+    && conference
246
+    && conference.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
247
+        // Even though we don't care too much about the failure, it may be
248
+        // good to know that it happen, so log it (on the info level).
249
+        logger.info('JitsiConference.leave() rejected with:', reason);
250
+    });
251
+
252
+    // FIXME: Workaround for the web version. Currently, the creation of the
253
+    // conference is handled by /conference.js and appropriate failure handlers
254
+    // are set there.
255
+    if (typeof APP !== 'undefined') {
258
         _removeUnloadHandler(getState);
256
         _removeUnloadHandler(getState);
259
     }
257
     }
260
 
258
 
339
  * @private
337
  * @private
340
  * @returns {Object} The value returned by {@code next(action)}.
338
  * @returns {Object} The value returned by {@code next(action)}.
341
  */
339
  */
342
-function _connectionEstablished({ dispatch }: IStore, next: Function, action: AnyAction) {
340
+async function _connectionEstablished({ dispatch }: IStore, next: Function, action: AnyAction) {
343
     const result = next(action);
341
     const result = next(action);
344
 
342
 
345
     // FIXME: Workaround for the web version. Currently, the creation of the
343
     // FIXME: Workaround for the web version. Currently, the creation of the
346
     // conference is handled by /conference.js.
344
     // conference is handled by /conference.js.
347
-    typeof APP === 'undefined' && dispatch(createConference());
345
+    if (typeof APP === 'undefined') {
346
+        dispatch(createConference());
347
+
348
+        return result;
349
+    }
348
 
350
 
349
     return result;
351
     return result;
350
 }
352
 }
386
 function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
388
 function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
387
     _logJwtErrors(action.error.message, getState());
389
     _logJwtErrors(action.error.message, getState());
388
 
390
 
391
+    dispatch(showErrorNotification({
392
+        descriptionKey: 'dialog.tokenAuthFailed',
393
+        titleKey: 'dialog.tokenAuthFailedTitle'
394
+    }, NOTIFICATION_TIMEOUT_TYPE.LONG));
395
+
389
     const result = next(action);
396
     const result = next(action);
390
 
397
 
391
     _removeUnloadHandler(getState);
398
     _removeUnloadHandler(getState);
392
 
399
 
393
-    // FIXME: Workaround for the web version. Currently, the creation of the
394
-    // conference is handled by /conference.js and appropriate failure handlers
395
-    // are set there.
396
-    if (typeof APP === 'undefined') {
397
-        const { connection } = action;
398
-        const { error } = action;
399
-
400
-        forEachConference(getState, conference => {
401
-            // It feels that it would make things easier if JitsiConference
402
-            // in lib-jitsi-meet would monitor it's connection and emit
403
-            // CONFERENCE_FAILED when it's dropped. It has more knowledge on
404
-            // whether it can recover or not. But because the reload screen
405
-            // and the retry logic is implemented in the app maybe it can be
406
-            // left this way for now.
407
-            if (conference.getConnection() === connection) {
408
-                // XXX Note that on mobile the error type passed to
409
-                // connectionFailed is always an object with .name property.
410
-                // This fact needs to be checked prior to enabling this logic on
411
-                // web.
412
-                const conferenceAction
413
-                    = conferenceFailed(conference, error.name);
414
-
415
-                // Copy the recoverable flag if set on the CONNECTION_FAILED
416
-                // action to not emit recoverable action caused by
417
-                // a non-recoverable one.
418
-                if (typeof error.recoverable !== 'undefined') {
419
-                    conferenceAction.error.recoverable = error.recoverable;
420
-                }
421
-
422
-                dispatch(conferenceAction);
400
+    const { connection } = action;
401
+    const { error } = action;
402
+
403
+    forEachConference(getState, conference => {
404
+        // TODO: revisit this
405
+        // It feels that it would make things easier if JitsiConference
406
+        // in lib-jitsi-meet would monitor it's connection and emit
407
+        // CONFERENCE_FAILED when it's dropped. It has more knowledge on
408
+        // whether it can recover or not. But because the reload screen
409
+        // and the retry logic is implemented in the app maybe it can be
410
+        // left this way for now.
411
+        if (conference.getConnection() === connection) {
412
+            // XXX Note that on mobile the error type passed to
413
+            // connectionFailed is always an object with .name property.
414
+            // This fact needs to be checked prior to enabling this logic on
415
+            // web.
416
+            const conferenceAction = conferenceFailed(conference, error.name);
417
+
418
+            // Copy the recoverable flag if set on the CONNECTION_FAILED
419
+            // action to not emit recoverable action caused by
420
+            // a non-recoverable one.
421
+            if (typeof error.recoverable !== 'undefined') {
422
+                conferenceAction.error.recoverable = error.recoverable;
423
             }
423
             }
424
 
424
 
425
-            return true;
426
-        });
427
-    }
425
+            dispatch(conferenceAction);
426
+        }
427
+
428
+        return true;
429
+    });
428
 
430
 
429
     return result;
431
     return result;
430
 }
432
 }

+ 2
- 1
react/features/base/conference/middleware.web.ts Ver arquivo

9
     CONFERENCE_FAILED,
9
     CONFERENCE_FAILED,
10
     CONFERENCE_JOINED,
10
     CONFERENCE_JOINED,
11
     CONFERENCE_JOIN_IN_PROGRESS,
11
     CONFERENCE_JOIN_IN_PROGRESS,
12
-    CONFERENCE_LEFT, KICKED_OUT
12
+    CONFERENCE_LEFT,
13
+    KICKED_OUT
13
 } from './actionTypes';
14
 } from './actionTypes';
14
 import logger from './logger';
15
 import logger from './logger';
15
 import './middleware.any';
16
 import './middleware.any';

+ 3
- 0
react/features/base/conference/reducer.ts Ver arquivo

155
 
155
 
156
 export interface IJitsiConferenceRoom {
156
 export interface IJitsiConferenceRoom {
157
     locked: boolean;
157
     locked: boolean;
158
+    moderator: {
159
+        logout: Function;
160
+    };
158
     myroomjid: string;
161
     myroomjid: string;
159
     roomjid: string;
162
     roomjid: string;
160
 }
163
 }

+ 198
- 1
react/features/base/connection/actions.any.ts Ver arquivo

1
 import _ from 'lodash';
1
 import _ from 'lodash';
2
 
2
 
3
-import { IReduxState } from '../../app/types';
3
+import { IReduxState, IStore } from '../../app/types';
4
+import { setPrejoinDisplayNameRequired } from '../../prejoin/actions.any';
5
+import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
6
+import { getCurrentConference } from '../conference/functions';
7
+import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
4
 import {
8
 import {
5
     appendURLParam,
9
     appendURLParam,
6
     getBackendSafeRoomName
10
     getBackendSafeRoomName
10
     CONNECTION_DISCONNECTED,
14
     CONNECTION_DISCONNECTED,
11
     CONNECTION_ESTABLISHED,
15
     CONNECTION_ESTABLISHED,
12
     CONNECTION_FAILED,
16
     CONNECTION_FAILED,
17
+    CONNECTION_WILL_CONNECT,
13
     SET_LOCATION_URL
18
     SET_LOCATION_URL
14
 } from './actionTypes';
19
 } from './actionTypes';
20
+import { JITSI_CONNECTION_URL_KEY } from './constants';
15
 import logger from './logger';
21
 import logger from './logger';
16
 
22
 
17
 /**
23
 /**
194
         locationURL
200
         locationURL
195
     };
201
     };
196
 }
202
 }
203
+
204
+/**
205
+ * Opens new connection.
206
+ *
207
+ * @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
208
+ * @param {string} [password] - The XMPP user's password.
209
+ * @returns {Function}
210
+ */
211
+export function _connectInternal(id?: string, password?: string) {
212
+    return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
213
+        const state = getState();
214
+        const options = constructOptions(state);
215
+        const { locationURL } = state['features/base/connection'];
216
+        const { jwt } = state['features/base/jwt'];
217
+
218
+        const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
219
+
220
+        connection[JITSI_CONNECTION_URL_KEY] = locationURL;
221
+
222
+        dispatch(_connectionWillConnect(connection));
223
+
224
+        return new Promise((resolve, reject) => {
225
+            connection.addEventListener(
226
+                JitsiConnectionEvents.CONNECTION_DISCONNECTED,
227
+                _onConnectionDisconnected);
228
+            connection.addEventListener(
229
+                JitsiConnectionEvents.CONNECTION_ESTABLISHED,
230
+                _onConnectionEstablished);
231
+            connection.addEventListener(
232
+                JitsiConnectionEvents.CONNECTION_FAILED,
233
+                _onConnectionFailed);
234
+
235
+            /**
236
+             * Marks the display name for the prejoin screen as required.
237
+             * This can happen if a user tries to join a room with lobby enabled.
238
+             */
239
+            connection.addEventListener(
240
+                JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
241
+                () => dispatch(setPrejoinDisplayNameRequired())
242
+            );
243
+
244
+            /**
245
+             * Unsubscribe the connection instance from
246
+             * {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
247
+             *
248
+             * @returns {void}
249
+             */
250
+            function unsubscribe() {
251
+                connection.removeEventListener(
252
+                    JitsiConnectionEvents.CONNECTION_DISCONNECTED, _onConnectionDisconnected);
253
+                connection.removeEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _onConnectionFailed);
254
+            }
255
+
256
+            /**
257
+             * Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
258
+             * disconnected.
259
+             *
260
+             * @private
261
+             * @returns {void}
262
+             */
263
+            function _onConnectionDisconnected() {
264
+                unsubscribe();
265
+                dispatch(connectionDisconnected(connection));
266
+                resolve(connection);
267
+            }
268
+
269
+            /**
270
+             * Rejects external promise when connection fails.
271
+             *
272
+             * @param {JitsiConnectionErrors} err - Connection error.
273
+             * @param {string} [message] - Error message supplied by lib-jitsi-meet.
274
+             * @param {Object} [credentials] - The invalid credentials that were
275
+             * used to authenticate and the authentication failed.
276
+             * @param {string} [credentials.jid] - The XMPP user's ID.
277
+             * @param {string} [credentials.password] - The XMPP user's password.
278
+             * @param {Object} details - Additional information about the error.
279
+             * @private
280
+             * @returns {void}
281
+             */
282
+            function _onConnectionFailed( // eslint-disable-line max-params
283
+                    err: string,
284
+                    message: string,
285
+                    credentials: any,
286
+                    details: Object) {
287
+                unsubscribe();
288
+
289
+                dispatch(connectionFailed(connection, {
290
+                    credentials,
291
+                    details,
292
+                    name: err,
293
+                    message
294
+                }));
295
+
296
+                reject(err);
297
+            }
298
+
299
+            /**
300
+             * Resolves external promise when connection is established.
301
+             *
302
+             * @private
303
+             * @returns {void}
304
+             */
305
+            function _onConnectionEstablished() {
306
+                connection.removeEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED, _onConnectionEstablished);
307
+                dispatch(connectionEstablished(connection, Date.now()));
308
+                resolve(connection);
309
+            }
310
+
311
+            connection.connect({
312
+                id,
313
+                password
314
+            });
315
+        });
316
+    };
317
+}
318
+
319
+/**
320
+ * Create an action for when a connection will connect.
321
+ *
322
+ * @param {JitsiConnection} connection - The {@code JitsiConnection} which will
323
+ * connect.
324
+ * @private
325
+ * @returns {{
326
+ *     type: CONNECTION_WILL_CONNECT,
327
+ *     connection: JitsiConnection
328
+ * }}
329
+ */
330
+function _connectionWillConnect(connection: Object) {
331
+    return {
332
+        type: CONNECTION_WILL_CONNECT,
333
+        connection
334
+    };
335
+}
336
+
337
+/**
338
+ * Closes connection.
339
+ *
340
+ * @returns {Function}
341
+ */
342
+export function disconnect() {
343
+    return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
344
+        const state = getState();
345
+
346
+        // The conference we have already joined or are joining.
347
+        const conference_ = getCurrentConference(state);
348
+
349
+        // Promise which completes when the conference has been left and the
350
+        // connection has been disconnected.
351
+        let promise;
352
+
353
+        // Leave the conference.
354
+        if (conference_) {
355
+            // In a fashion similar to JitsiConference's CONFERENCE_LEFT event
356
+            // (and the respective Redux action) which is fired after the
357
+            // conference has been left, notify the application about the
358
+            // intention to leave the conference.
359
+            dispatch(conferenceWillLeave(conference_));
360
+
361
+            promise
362
+                = conference_.leave()
363
+                .catch((error: Error) => {
364
+                    logger.warn(
365
+                        'JitsiConference.leave() rejected with:',
366
+                        error);
367
+
368
+                    // The library lib-jitsi-meet failed to make the
369
+                    // JitsiConference leave. Which may be because
370
+                    // JitsiConference thinks it has already left.
371
+                    // Regardless of the failure reason, continue in
372
+                    // jitsi-meet as if the leave has succeeded.
373
+                    dispatch(conferenceLeft(conference_));
374
+                });
375
+        } else {
376
+            promise = Promise.resolve();
377
+        }
378
+
379
+        // Disconnect the connection.
380
+        const { connecting, connection } = state['features/base/connection'];
381
+
382
+        // The connection we have already connected or are connecting.
383
+        const connection_ = connection || connecting;
384
+
385
+        if (connection_) {
386
+            promise = promise.then(() => connection_.disconnect());
387
+        } else {
388
+            logger.info('No connection found while disconnecting.');
389
+        }
390
+
391
+        return promise;
392
+    };
393
+}

+ 8
- 182
react/features/base/connection/actions.native.ts Ver arquivo

1
+import { appNavigate } from '../../app/actions.native';
1
 import { IStore } from '../../app/types';
2
 import { IStore } from '../../app/types';
2
-import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
3
-import { getCurrentConference } from '../conference/functions';
4
-import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
5
 
3
 
6
-import {
7
-    CONNECTION_WILL_CONNECT
8
-} from './actionTypes';
9
-import {
10
-    connectionDisconnected,
11
-    connectionEstablished,
12
-    connectionFailed,
13
-    constructOptions
14
-} from './actions.any';
15
-import { JITSI_CONNECTION_URL_KEY } from './constants';
16
-import logger from './logger';
4
+import { _connectInternal } from './actions.any';
17
 
5
 
18
 export * from './actions.any';
6
 export * from './actions.any';
19
 
7
 
25
  * @returns {Function}
13
  * @returns {Function}
26
  */
14
  */
27
 export function connect(id?: string, password?: string) {
15
 export function connect(id?: string, password?: string) {
28
-    return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
29
-        const state = getState();
30
-        const options = constructOptions(state);
31
-        const { locationURL } = state['features/base/connection'];
32
-        const { jwt } = state['features/base/jwt'];
33
-        const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
34
-
35
-        connection[JITSI_CONNECTION_URL_KEY] = locationURL;
36
-
37
-        dispatch(_connectionWillConnect(connection));
38
-
39
-        connection.addEventListener(
40
-            JitsiConnectionEvents.CONNECTION_DISCONNECTED,
41
-            _onConnectionDisconnected);
42
-        connection.addEventListener(
43
-            JitsiConnectionEvents.CONNECTION_ESTABLISHED,
44
-            _onConnectionEstablished);
45
-        connection.addEventListener(
46
-            JitsiConnectionEvents.CONNECTION_FAILED,
47
-            _onConnectionFailed);
48
-
49
-        connection.connect({
50
-            id,
51
-            password
52
-        });
53
-
54
-        /**
55
-         * Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
56
-         * disconnected.
57
-         *
58
-         * @private
59
-         * @returns {void}
60
-         */
61
-        function _onConnectionDisconnected() {
62
-            unsubscribe();
63
-            dispatch(connectionDisconnected(connection));
64
-        }
65
-
66
-        /**
67
-         * Resolves external promise when connection is established.
68
-         *
69
-         * @private
70
-         * @returns {void}
71
-         */
72
-        function _onConnectionEstablished() {
73
-            connection.removeEventListener(
74
-                JitsiConnectionEvents.CONNECTION_ESTABLISHED,
75
-                _onConnectionEstablished);
76
-            dispatch(connectionEstablished(connection, Date.now()));
77
-        }
78
-
79
-        /**
80
-         * Rejects external promise when connection fails.
81
-         *
82
-         * @param {JitsiConnectionErrors} err - Connection error.
83
-         * @param {string} [msg] - Error message supplied by lib-jitsi-meet.
84
-         * @param {Object} [credentials] - The invalid credentials that were
85
-         * used to authenticate and the authentication failed.
86
-         * @param {string} [credentials.jid] - The XMPP user's ID.
87
-         * @param {string} [credentials.password] - The XMPP user's password.
88
-         * @param {Object} details - Additional information about the error.
89
-         * @private
90
-         * @returns {void}
91
-         */
92
-        function _onConnectionFailed( // eslint-disable-line max-params
93
-                err: string,
94
-                msg: string,
95
-                credentials: any,
96
-                details: Object) {
97
-            unsubscribe();
98
-            dispatch(
99
-                connectionFailed(
100
-                    connection, {
101
-                        credentials,
102
-                        details,
103
-                        name: err,
104
-                        message: msg
105
-                    }
106
-                ));
107
-        }
108
-
109
-        /**
110
-         * Unsubscribe the connection instance from
111
-         * {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
112
-         *
113
-         * @returns {void}
114
-         */
115
-        function unsubscribe() {
116
-            connection.removeEventListener(
117
-                JitsiConnectionEvents.CONNECTION_DISCONNECTED,
118
-                _onConnectionDisconnected);
119
-            connection.removeEventListener(
120
-                JitsiConnectionEvents.CONNECTION_FAILED,
121
-                _onConnectionFailed);
122
-        }
123
-    };
124
-}
125
-
126
-/**
127
- * Create an action for when a connection will connect.
128
- *
129
- * @param {JitsiConnection} connection - The {@code JitsiConnection} which will
130
- * connect.
131
- * @private
132
- * @returns {{
133
- *     type: CONNECTION_WILL_CONNECT,
134
- *     connection: JitsiConnection
135
- * }}
136
- */
137
-function _connectionWillConnect(connection: Object) {
138
-    return {
139
-        type: CONNECTION_WILL_CONNECT,
140
-        connection
141
-    };
16
+    return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password));
142
 }
17
 }
143
 
18
 
144
 /**
19
 /**
145
- * Closes connection.
20
+ * Hangup.
146
  *
21
  *
147
- * @param {boolean} _ - Used in web.
22
+ * @param {boolean} [_requestFeedback] - Whether to attempt showing a
23
+ * request for call feedback.
148
  * @returns {Function}
24
  * @returns {Function}
149
  */
25
  */
150
-export function disconnect(_?: boolean) {
151
-    /* eslint-enable @typescript-eslint/no-unused-vars */
152
-    return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
153
-        const state = getState();
154
-
155
-        // The conference we have already joined or are joining.
156
-        const conference_ = getCurrentConference(state);
157
-
158
-        // Promise which completes when the conference has been left and the
159
-        // connection has been disconnected.
160
-        let promise;
161
-
162
-        // Leave the conference.
163
-        if (conference_) {
164
-            // In a fashion similar to JitsiConference's CONFERENCE_LEFT event
165
-            // (and the respective Redux action) which is fired after the
166
-            // conference has been left, notify the application about the
167
-            // intention to leave the conference.
168
-            dispatch(conferenceWillLeave(conference_));
169
-
170
-            promise
171
-                = conference_.leave()
172
-                    .catch((error: Error) => {
173
-                        logger.warn(
174
-                            'JitsiConference.leave() rejected with:',
175
-                            error);
176
-
177
-                        // The library lib-jitsi-meet failed to make the
178
-                        // JitsiConference leave. Which may be because
179
-                        // JitsiConference thinks it has already left.
180
-                        // Regardless of the failure reason, continue in
181
-                        // jitsi-meet as if the leave has succeeded.
182
-                        dispatch(conferenceLeft(conference_));
183
-                    });
184
-        } else {
185
-            promise = Promise.resolve();
186
-        }
187
-
188
-        // Disconnect the connection.
189
-        const { connecting, connection } = state['features/base/connection'];
190
-
191
-        // The connection we have already connected or are connecting.
192
-        const connection_ = connection || connecting;
193
-
194
-        if (connection_) {
195
-            promise = promise.then(() => connection_.disconnect());
196
-        } else {
197
-            logger.info('No connection found while disconnecting.');
198
-        }
199
-
200
-        return promise;
201
-    };
26
+export function hangup(_requestFeedback = false) {
27
+    return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
202
 }
28
 }

+ 40
- 25
react/features/base/connection/actions.web.ts Ver arquivo

1
+// @ts-expect-error
2
+import { jitsiLocalStorage } from '@jitsi/js-utils';
3
+
1
 import { IStore } from '../../app/types';
4
 import { IStore } from '../../app/types';
5
+import { getCustomerDetails } from '../../jaas/actions.any';
6
+import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions';
2
 import { showWarningNotification } from '../../notifications/actions';
7
 import { showWarningNotification } from '../../notifications/actions';
3
 import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
8
 import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
4
 import { stopLocalVideoRecording } from '../../recording/actions.any';
9
 import { stopLocalVideoRecording } from '../../recording/actions.any';
5
 import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web';
10
 import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web';
6
-import { configureInitialDevices } from '../devices/actions';
7
-import { getBackendSafeRoomName } from '../util/uri';
11
+import { setJWT } from '../jwt/actions';
8
 
12
 
9
-export {
10
-    connectionDisconnected,
11
-    connectionEstablished,
12
-    connectionFailed,
13
-    setLocationURL
14
-} from './actions.any';
15
-import logger from './logger';
13
+import { _connectInternal } from './actions.any';
16
 
14
 
17
 export * from './actions.any';
15
 export * from './actions.any';
18
 
16
 
19
 /**
17
 /**
20
  * Opens new connection.
18
  * Opens new connection.
21
  *
19
  *
22
- * @returns {Promise<JitsiConnection>}
20
+ * @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
21
+ * @param {string} [password] - The XMPP user's password.
22
+ * @returns {Function}
23
  */
23
  */
24
-export function connect() {
24
+export function connect(id?: string, password?: string) {
25
     return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
25
     return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
26
-        const room = getBackendSafeRoomName(getState()['features/base/conference'].room);
26
+        const state = getState();
27
+        const { jwt } = state['features/base/jwt'];
28
+        const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
29
+
30
+        if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
31
+            return dispatch(getCustomerDetails())
32
+                .then(() => {
33
+                    if (!jwt) {
34
+                        return getJaasJWT(state);
35
+                    }
36
+                })
37
+                .then(j => j && dispatch(setJWT(j)))
38
+                .then(() => _connectInternal(id, password));
39
+        }
40
+
41
+        // used by jibri
42
+        const usernameOverride = jitsiLocalStorage.getItem('xmpp_username_override');
43
+        const passwordOverride = jitsiLocalStorage.getItem('xmpp_password_override');
44
+
45
+        if (usernameOverride && usernameOverride.length > 0) {
46
+            id = usernameOverride; // eslint-disable-line no-param-reassign
47
+        }
48
+        if (passwordOverride && passwordOverride.length > 0) {
49
+            password = passwordOverride; // eslint-disable-line no-param-reassign
50
+        }
27
 
51
 
28
-        // XXX For web based version we use conference initialization logic
29
-        // from the old app (at the moment of writing).
30
-        return dispatch(configureInitialDevices()).then(
31
-            () => APP.conference.init({
32
-                roomName: room
33
-            }).catch((error: Error) => {
34
-                APP.API.notifyConferenceLeft(APP.conference.roomName);
35
-                logger.error(error);
36
-            }));
52
+        return dispatch(_connectInternal(id, password));
37
     };
53
     };
38
 }
54
 }
39
 
55
 
40
 /**
56
 /**
41
  * Closes connection.
57
  * Closes connection.
42
  *
58
  *
43
- * @param {boolean} [requestFeedback] - Whether or not to attempt showing a
59
+ * @param {boolean} [requestFeedback] - Whether to attempt showing a
44
  * request for call feedback.
60
  * request for call feedback.
45
  * @returns {Function}
61
  * @returns {Function}
46
  */
62
  */
47
-export function disconnect(requestFeedback = false) {
48
-    // XXX For web based version we use conference hanging up logic from the old
49
-    // app.
63
+export function hangup(requestFeedback = false) {
64
+    // XXX For web based version we use conference hanging up logic from the old app.
50
     return async (dispatch: IStore['dispatch']) => {
65
     return async (dispatch: IStore['dispatch']) => {
51
         if (LocalRecordingManager.isRecordingLocally()) {
66
         if (LocalRecordingManager.isRecordingLocally()) {
52
             dispatch(stopLocalVideoRecording());
67
             dispatch(stopLocalVideoRecording());

+ 30
- 0
react/features/base/connection/middleware.web.ts Ver arquivo

1
+import MiddlewareRegistry from '../redux/MiddlewareRegistry';
2
+
3
+import { CONNECTION_WILL_CONNECT } from './actionTypes';
4
+
5
+/**
6
+ * The feature announced so we can distinguish jibri participants.
7
+ *
8
+ * @type {string}
9
+ */
10
+export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
11
+
12
+MiddlewareRegistry.register(({ getState }) => next => action => {
13
+    switch (action.type) {
14
+    case CONNECTION_WILL_CONNECT: {
15
+        const { connection } = action;
16
+        const { iAmRecorder } = getState()['features/base/config'];
17
+
18
+        if (iAmRecorder) {
19
+            connection.addFeature(DISCO_JIBRI_FEATURE);
20
+        }
21
+
22
+        // @ts-ignore
23
+        APP.connection = connection;
24
+
25
+        break;
26
+    }
27
+    }
28
+
29
+    return next(action);
30
+});

+ 1
- 1
react/features/base/tracks/actions.any.ts Ver arquivo

142
         const promises = [];
142
         const promises = [];
143
 
143
 
144
         // The following executes on React Native only at the time of this
144
         // The following executes on React Native only at the time of this
145
-        // writing. The effort to port Web's createInitialLocalTracksAndConnect
145
+        // writing. The effort to port Web's createInitialLocalTracks
146
         // is significant and that's where the function createLocalTracksF got
146
         // is significant and that's where the function createLocalTracksF got
147
         // born. I started with the idea a porting so that we could inherit the
147
         // born. I started with the idea a porting so that we could inherit the
148
         // ability to getUserMedia for audio only or video only if getUserMedia
148
         // ability to getUserMedia for audio only or video only if getUserMedia

+ 24
- 0
react/features/conference/actions.web.ts Ver arquivo

1
 import { IStore } from '../app/types';
1
 import { IStore } from '../app/types';
2
+import { configureInitialDevices } from '../base/devices/actions.web';
2
 import { getParticipantDisplayName } from '../base/participants/functions';
3
 import { getParticipantDisplayName } from '../base/participants/functions';
4
+import { getBackendSafeRoomName } from '../base/util/uri';
3
 import { showNotification } from '../notifications/actions';
5
 import { showNotification } from '../notifications/actions';
4
 import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
6
 import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
5
 
7
 
6
 import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
8
 import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
9
+import logger from './logger';
7
 
10
 
8
 /**
11
 /**
9
  * Notify that we've been kicked out of the conference.
12
  * Notify that we've been kicked out of the conference.
45
         type: DISMISS_CALENDAR_NOTIFICATION
48
         type: DISMISS_CALENDAR_NOTIFICATION
46
     };
49
     };
47
 }
50
 }
51
+
52
+/**
53
+ * Init.
54
+ *
55
+ * @returns {Promise<JitsiConnection>}
56
+ */
57
+export function init() {
58
+    return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
59
+        const room = getBackendSafeRoomName(getState()['features/base/conference'].room);
60
+
61
+        // XXX For web based version we use conference initialization logic
62
+        // from the old app (at the moment of writing).
63
+        return dispatch(configureInitialDevices()).then(
64
+            () => APP.conference.init({
65
+                roomName: room
66
+            }).catch((error: Error) => {
67
+                APP.API.notifyConferenceLeft(APP.conference.roomName);
68
+                logger.error(error);
69
+            }));
70
+    };
71
+}

+ 4
- 3
react/features/conference/components/web/Conference.tsx Ver arquivo

7
 import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
7
 import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
8
 import { IReduxState, IStore } from '../../../app/types';
8
 import { IReduxState, IStore } from '../../../app/types';
9
 import { getConferenceNameForTitle } from '../../../base/conference/functions';
9
 import { getConferenceNameForTitle } from '../../../base/conference/functions';
10
-import { connect, disconnect } from '../../../base/connection/actions.web';
10
+import { hangup } from '../../../base/connection/actions.web';
11
 import { isMobileBrowser } from '../../../base/environment/utils';
11
 import { isMobileBrowser } from '../../../base/environment/utils';
12
 import { translate } from '../../../base/i18n/functions';
12
 import { translate } from '../../../base/i18n/functions';
13
 import { setColorAlpha } from '../../../base/util/helpers';
13
 import { setColorAlpha } from '../../../base/util/helpers';
29
 import Toolbox from '../../../toolbox/components/web/Toolbox';
29
 import Toolbox from '../../../toolbox/components/web/Toolbox';
30
 import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
30
 import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
31
 import { getCurrentLayout } from '../../../video-layout/functions.any';
31
 import { getCurrentLayout } from '../../../video-layout/functions.any';
32
+import { init } from '../../actions.web';
32
 import { maybeShowSuboptimalExperienceNotification } from '../../functions.web';
33
 import { maybeShowSuboptimalExperienceNotification } from '../../functions.web';
33
 import {
34
 import {
34
     AbstractConference,
35
     AbstractConference,
187
         FULL_SCREEN_EVENTS.forEach(name =>
188
         FULL_SCREEN_EVENTS.forEach(name =>
188
             document.removeEventListener(name, this._onFullScreenChange));
189
             document.removeEventListener(name, this._onFullScreenChange));
189
 
190
 
190
-        APP.conference.isJoined() && this.props.dispatch(disconnect());
191
+        APP.conference.isJoined() && this.props.dispatch(hangup());
191
     }
192
     }
192
 
193
 
193
     /**
194
     /**
374
 
375
 
375
         const { dispatch, t } = this.props;
376
         const { dispatch, t } = this.props;
376
 
377
 
377
-        dispatch(connect());
378
+        dispatch(init());
378
 
379
 
379
         maybeShowSuboptimalExperienceNotification(dispatch, t);
380
         maybeShowSuboptimalExperienceNotification(dispatch, t);
380
     }
381
     }

+ 14
- 0
react/features/prejoin/actions.any.ts Ver arquivo

1
+import {
2
+    SET_PREJOIN_DISPLAY_NAME_REQUIRED
3
+} from './actionTypes';
4
+
5
+/**
6
+ * Action used to set the stance of the display name.
7
+ *
8
+ * @returns {Object}
9
+ */
10
+export function setPrejoinDisplayNameRequired() {
11
+    return {
12
+        type: SET_PREJOIN_DISPLAY_NAME_REQUIRED
13
+    };
14
+}

+ 27
- 32
react/features/prejoin/actions.web.ts Ver arquivo

3
 import { IStore } from '../app/types';
3
 import { IStore } from '../app/types';
4
 import { updateConfig } from '../base/config/actions';
4
 import { updateConfig } from '../base/config/actions';
5
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
5
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
6
+import { connect } from '../base/connection/actions';
6
 import { browser } from '../base/lib-jitsi-meet';
7
 import { browser } from '../base/lib-jitsi-meet';
7
 import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
8
 import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
8
 import { MEDIA_TYPE } from '../base/media/constants';
9
 import { MEDIA_TYPE } from '../base/media/constants';
31
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
32
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
32
     SET_PRECALL_TEST_RESULTS,
33
     SET_PRECALL_TEST_RESULTS,
33
     SET_PREJOIN_DEVICE_ERRORS,
34
     SET_PREJOIN_DEVICE_ERRORS,
34
-    SET_PREJOIN_DISPLAY_NAME_REQUIRED,
35
     SET_PREJOIN_PAGE_VISIBILITY,
35
     SET_PREJOIN_PAGE_VISIBILITY,
36
     SET_SKIP_PREJOIN_RELOAD
36
     SET_SKIP_PREJOIN_RELOAD
37
 } from './actionTypes';
37
 } from './actionTypes';
66
  */
66
  */
67
 const STATUS_REQ_CAP = 45;
67
 const STATUS_REQ_CAP = 45;
68
 
68
 
69
+export * from './actions.any';
70
+
69
 /**
71
 /**
70
  * Polls for status change after dial out.
72
  * Polls for status change after dial out.
71
  * Changes dialog message based on response, closes the dialog if there is an error,
73
  * Changes dialog message based on response, closes the dialog if there is an error,
226
             dispatch(setJoiningInProgress(true));
228
             dispatch(setJoiningInProgress(true));
227
         }
229
         }
228
 
230
 
229
-        const state = getState();
230
-        let localTracks = getLocalTracks(state['features/base/tracks']);
231
-
232
         options && dispatch(updateConfig(options));
231
         options && dispatch(updateConfig(options));
233
 
232
 
234
-        // Do not signal audio/video tracks if the user joins muted.
235
-        for (const track of localTracks) {
236
-            // Always add the audio track on Safari because of a known issue where audio playout doesn't happen
237
-            // if the user joins audio and video muted.
238
-            if (track.muted
239
-                && !(browser.isWebKitBased() && track.jitsiTrack && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) {
240
-                try {
241
-                    await dispatch(replaceLocalTrack(track.jitsiTrack, null));
242
-                } catch (error) {
243
-                    logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`);
233
+        dispatch(connect()).then(async () => {
234
+            // TODO keep this here till we move tracks and conference management from
235
+            // conference.js to react.
236
+            const state = getState();
237
+            let localTracks = getLocalTracks(state['features/base/tracks']);
238
+
239
+            // Do not signal audio/video tracks if the user joins muted.
240
+            for (const track of localTracks) {
241
+                // Always add the audio track on Safari because of a known issue where audio playout doesn't happen
242
+                // if the user joins audio and video muted.
243
+                if (track.muted && !(browser.isWebKitBased() && track.jitsiTrack
244
+                        && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) {
245
+                    try {
246
+                        await dispatch(replaceLocalTrack(track.jitsiTrack, null));
247
+                    } catch (error) {
248
+                        logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`);
249
+                    }
244
                 }
250
                 }
245
             }
251
             }
246
-        }
247
 
252
 
248
-        // Re-fetch the local tracks after muted tracks have been removed above.
249
-        // This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should not be used
250
-        // anymore.
251
-        localTracks = getLocalTracks(getState()['features/base/tracks']);
253
+            // Re-fetch the local tracks after muted tracks have been removed above.
254
+            // This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should not be
255
+            // used anymore.
256
+            localTracks = getLocalTracks(getState()['features/base/tracks']);
252
 
257
 
253
-        const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
258
+            const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
254
 
259
 
255
-        APP.conference.prejoinStart(jitsiTracks);
260
+            APP.conference.startConference(jitsiTracks).catch(logger.error);
261
+        });
256
     };
262
     };
257
 }
263
 }
258
 
264
 
465
     };
471
     };
466
 }
472
 }
467
 
473
 
468
-/**
469
- * Action used to set the stance of the display name.
470
- *
471
- * @returns {Object}
472
- */
473
-export function setPrejoinDisplayNameRequired() {
474
-    return {
475
-        type: SET_PREJOIN_DISPLAY_NAME_REQUIRED
476
-    };
477
-}
478
-
479
 /**
474
 /**
480
  * Action used to set the dial out number.
475
  * Action used to set the dial out number.
481
  *
476
  *

+ 3
- 3
react/features/room-lock/middleware.ts Ver arquivo

12
 import { hideDialog } from '../base/dialog/actions';
12
 import { hideDialog } from '../base/dialog/actions';
13
 import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
13
 import { JitsiConferenceErrors } from '../base/lib-jitsi-meet';
14
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
14
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
15
-import { showNotification } from '../notifications/actions';
15
+import { showErrorNotification, showNotification } from '../notifications/actions';
16
 import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
16
 import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
17
 
17
 
18
 import { _openPasswordRequiredPrompt } from './actions';
18
 import { _openPasswordRequiredPrompt } from './actions';
147
             descriptionKey = 'dialog.lockMessage';
147
             descriptionKey = 'dialog.lockMessage';
148
             titleKey = 'dialog.lockTitle';
148
             titleKey = 'dialog.lockTitle';
149
         }
149
         }
150
-        APP.UI.messageHandler.showError({
150
+        APP.store.dispatch(showErrorNotification({
151
             descriptionKey,
151
             descriptionKey,
152
             titleKey
152
             titleKey
153
-        });
153
+        }, NOTIFICATION_TIMEOUT_TYPE.LONG));
154
     }
154
     }
155
 
155
 
156
     return next(action);
156
     return next(action);

+ 10
- 0
react/features/settings/actions.native.ts Ver arquivo

1
+/**
2
+ * Opens {@code LogoutDialog}.
3
+ *
4
+ * @param {Function} _onLogout - The event in {@code LogoutDialog} that should be
5
+ *  enabled on click.
6
+ * @returns {Function}
7
+ */
8
+export function openLogoutDialog(_onLogout: Function): any {
9
+    // this is available only for web at the moment
10
+}

+ 12
- 9
react/features/settings/components/web/ProfileTab.tsx Ver arquivo

2
 import { withStyles } from '@mui/styles';
2
 import { withStyles } from '@mui/styles';
3
 import React from 'react';
3
 import React from 'react';
4
 import { WithTranslation } from 'react-i18next';
4
 import { WithTranslation } from 'react-i18next';
5
+import { connect } from 'react-redux';
5
 
6
 
6
-// @ts-expect-error
7
-import UIEvents from '../../../../../service/UI/UIEvents';
8
 import { createProfilePanelButtonEvent } from '../../../analytics/AnalyticsEvents';
7
 import { createProfilePanelButtonEvent } from '../../../analytics/AnalyticsEvents';
9
 import { sendAnalytics } from '../../../analytics/functions';
8
 import { sendAnalytics } from '../../../analytics/functions';
9
+import { IStore } from '../../../app/types';
10
+import { login, logout } from '../../../authentication/actions.web';
10
 import Avatar from '../../../base/avatar/components/Avatar';
11
 import Avatar from '../../../base/avatar/components/Avatar';
11
 import AbstractDialogTab, {
12
 import AbstractDialogTab, {
12
     IProps as AbstractDialogTabProps } from '../../../base/dialog/components/web/AbstractDialogTab';
13
     IProps as AbstractDialogTabProps } from '../../../base/dialog/components/web/AbstractDialogTab';
14
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
15
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
15
 import Button from '../../../base/ui/components/web/Button';
16
 import Button from '../../../base/ui/components/web/Button';
16
 import Input from '../../../base/ui/components/web/Input';
17
 import Input from '../../../base/ui/components/web/Input';
17
-import { openLogoutDialog } from '../../actions';
18
 
18
 
19
 /**
19
 /**
20
  * The type of the React {@code Component} props of {@link ProfileTab}.
20
  * The type of the React {@code Component} props of {@link ProfileTab}.
22
 export interface IProps extends AbstractDialogTabProps, WithTranslation {
22
 export interface IProps extends AbstractDialogTabProps, WithTranslation {
23
 
23
 
24
     /**
24
     /**
25
-     * Whether or not server-side authentication is available.
25
+     * Whether server-side authentication is available.
26
      */
26
      */
27
     authEnabled: boolean;
27
     authEnabled: boolean;
28
 
28
 
36
      */
36
      */
37
     classes: any;
37
     classes: any;
38
 
38
 
39
+    /**
40
+     * Invoked to change the configured calendar integration.
41
+     */
42
+    dispatch: IStore['dispatch'];
43
+
39
     /**
44
     /**
40
      * The display name to display for the local participant.
45
      * The display name to display for the local participant.
41
      */
46
      */
204
         if (this.props.authLogin) {
209
         if (this.props.authLogin) {
205
             sendAnalytics(createProfilePanelButtonEvent('logout.button'));
210
             sendAnalytics(createProfilePanelButtonEvent('logout.button'));
206
 
211
 
207
-            APP.store.dispatch(openLogoutDialog(
208
-                () => APP.UI.emitEvent(UIEvents.LOGOUT)
209
-            ));
212
+            this.props.dispatch(logout());
210
         } else {
213
         } else {
211
             sendAnalytics(createProfilePanelButtonEvent('login.button'));
214
             sendAnalytics(createProfilePanelButtonEvent('login.button'));
212
 
215
 
213
-            APP.UI.emitEvent(UIEvents.AUTH_CLICKED);
216
+            this.props.dispatch(login());
214
         }
217
         }
215
     }
218
     }
216
 
219
 
246
     }
249
     }
247
 }
250
 }
248
 
251
 
249
-export default withStyles(styles)(translate(ProfileTab));
252
+export default withStyles(styles)(translate(connect()(ProfileTab)));

+ 6
- 1
react/features/video-layout/middleware.web.ts Ver arquivo

1
 // @ts-expect-error
1
 // @ts-expect-error
2
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js';
2
 import VideoLayout from '../../../modules/UI/videolayout/VideoLayout.js';
3
-import { CONFERENCE_WILL_LEAVE } from '../base/conference/actionTypes';
3
+import { CONFERENCE_WILL_INIT, CONFERENCE_WILL_LEAVE } from '../base/conference/actionTypes';
4
 import { MEDIA_TYPE } from '../base/media/constants';
4
 import { MEDIA_TYPE } from '../base/media/constants';
5
 import { PARTICIPANT_JOINED } from '../base/participants/actionTypes';
5
 import { PARTICIPANT_JOINED } from '../base/participants/actionTypes';
6
 import { getLocalParticipant } from '../base/participants/functions';
6
 import { getLocalParticipant } from '../base/participants/functions';
25
     const result = next(action);
25
     const result = next(action);
26
 
26
 
27
     switch (action.type) {
27
     switch (action.type) {
28
+    case CONFERENCE_WILL_INIT:
29
+        // Reset VideoLayout. It's destroyed on CONFERENCE_WILL_LEAVE so re-initialize it.
30
+        VideoLayout.initLargeVideo();
31
+        VideoLayout.resizeVideoArea();
32
+        break;
28
     case CONFERENCE_WILL_LEAVE:
33
     case CONFERENCE_WILL_LEAVE:
29
         VideoLayout.reset();
34
         VideoLayout.reset();
30
         break;
35
         break;

+ 0
- 2
service/UI/UIEvents.js Ver arquivo

20
      */
20
      */
21
     TOGGLE_FULLSCREEN: 'UI.toogle_fullscreen',
21
     TOGGLE_FULLSCREEN: 'UI.toogle_fullscreen',
22
     FULLSCREEN_TOGGLED: 'UI.fullscreen_toggled',
22
     FULLSCREEN_TOGGLED: 'UI.fullscreen_toggled',
23
-    AUTH_CLICKED: 'UI.auth_clicked',
24
 
23
 
25
     /**
24
     /**
26
      * Notifies that the audio only mode was toggled.
25
      * Notifies that the audio only mode was toggled.
41
     TOGGLE_FILMSTRIP: 'UI.toggle_filmstrip',
40
     TOGGLE_FILMSTRIP: 'UI.toggle_filmstrip',
42
 
41
 
43
     HANGUP: 'UI.hangup',
42
     HANGUP: 'UI.hangup',
44
-    LOGOUT: 'UI.logout',
45
     VIDEO_DEVICE_CHANGED: 'UI.video_device_changed',
43
     VIDEO_DEVICE_CHANGED: 'UI.video_device_changed',
46
     AUDIO_DEVICE_CHANGED: 'UI.audio_device_changed',
44
     AUDIO_DEVICE_CHANGED: 'UI.audio_device_changed',
47
 
45
 

Carregando…
Cancelar
Salvar