Bläddra i källkod

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 år sedan
förälder
incheckning
bc23f9cd33
Inget konto är kopplat till bidragsgivarens mejladress
38 ändrade filer med 706 tillägg och 1462 borttagningar
  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 Visa fil

@@ -4,12 +4,8 @@ import { jitsiLocalStorage } from '@jitsi/js-utils';
4 4
 import Logger from '@jitsi/logger';
5 5
 import EventEmitter from 'events';
6 6
 
7
-import { openConnection } from './connection';
8 7
 import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
9 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 9
 import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
14 10
 import Recorder from './modules/recorder/Recorder';
15 11
 import { createTaskQueue } from './modules/util/helpers';
@@ -37,7 +33,7 @@ import {
37 33
     conferenceSubjectChanged,
38 34
     conferenceTimestampChanged,
39 35
     conferenceUniqueIdSet,
40
-    conferenceWillJoin,
36
+    conferenceWillInit,
41 37
     conferenceWillLeave,
42 38
     dataChannelClosed,
43 39
     dataChannelOpened,
@@ -57,12 +53,10 @@ import {
57 53
     commonUserJoinedHandling,
58 54
     commonUserLeftHandling,
59 55
     getConferenceOptions,
60
-    getVisitorOptions,
61
-    restoreConferenceOptions,
62 56
     sendLocalParticipant
63 57
 } from './react/features/base/conference/functions';
64
-import { overwriteConfig } from './react/features/base/config/actions';
65 58
 import { getReplaceParticipant } from './react/features/base/config/functions';
59
+import { connect } from './react/features/base/connection/actions.web';
66 60
 import {
67 61
     checkAndNotifyForNewDevice,
68 62
     getAvailableDevices,
@@ -77,15 +71,12 @@ import {
77 71
 import {
78 72
     JitsiConferenceErrors,
79 73
     JitsiConferenceEvents,
80
-    JitsiConnectionErrors,
81
-    JitsiConnectionEvents,
82 74
     JitsiE2ePingEvents,
83 75
     JitsiMediaDevicesEvents,
84 76
     JitsiTrackErrors,
85 77
     JitsiTrackEvents,
86 78
     browser
87 79
 } from './react/features/base/lib-jitsi-meet';
88
-import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
89 80
 import {
90 81
     gumPending,
91 82
     setAudioAvailable,
@@ -145,7 +136,12 @@ import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedba
145 136
 import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
146 137
 import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
147 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 145
 import {
150 146
     DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
151 147
     NOTIFICATION_TIMEOUT_TYPE
@@ -153,7 +149,7 @@ import {
153 149
 import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
154 150
 import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
155 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 153
 import { isPrejoinPageVisible } from './react/features/prejoin/functions';
158 154
 import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
159 155
 import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
@@ -164,7 +160,6 @@ import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise'
164 160
 import { endpointMessageReceived } from './react/features/subtitles/actions.any';
165 161
 import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
166 162
 import { muteLocal } from './react/features/video-menu/actions.any';
167
-import { setIAmVisitor } from './react/features/visitors/actions';
168 163
 import { iAmVisitor } from './react/features/visitors/functions';
169 164
 import UIEvents from './service/UI/UIEvents';
170 165
 
@@ -173,25 +168,6 @@ const logger = Logger.getLogger(__filename);
173 168
 const eventEmitter = new EventEmitter();
174 169
 
175 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 173
  * Logic to open a desktop picker put on the window global for
@@ -213,26 +189,6 @@ const commands = {
213 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 193
  * Share data to other users.
238 194
  * @param command the command
@@ -326,63 +282,25 @@ class ConferenceConnector {
326 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 285
         case JitsiConferenceErrors.RESERVATION_ERROR: {
349 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 296
             break;
382 297
         }
383 298
 
384 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 304
             break;
387 305
 
388 306
         // FIXME FOCUS_DISCONNECTED is a confusing event name.
@@ -408,31 +326,9 @@ class ConferenceConnector {
408 326
             // FIXME the conference should be stopped by the library and not by
409 327
             // the app. Both the errors above are unrecoverable from the library
410 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 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 332
         case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
437 333
             APP.store.dispatch(reloadWithStoredParams());
438 334
             break;
@@ -488,11 +384,11 @@ function disconnect() {
488 384
         return Promise.resolve();
489 385
     };
490 386
 
491
-    if (!connection) {
387
+    if (!APP.connection) {
492 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,25 +406,6 @@ function setGUMPendingStateOnFailedTracks(tracks) {
510 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 409
 export default {
533 410
     /**
534 411
      * Flag used to delay modification of the muted status of local media tracks
@@ -547,7 +424,16 @@ export default {
547 424
     /**
548 425
      * Returns an object containing a promise which resolves with the created tracks &
549 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 437
      * @returns {Promise<JitsiLocalTrack[]>, Object}
552 438
      */
553 439
     createInitialLocalTracks(options = {}) {
@@ -725,37 +611,7 @@ export default {
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 615
         tracks.forEach(track => {
760 616
             if ((track.isAudioTrack() && this.isLocalAudioMuted())
761 617
                 || (track.isVideoTrack() && this.isLocalVideoMuted())) {
@@ -768,9 +624,6 @@ export default {
768 624
             }
769 625
         });
770 626
 
771
-        con.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _connectionFailedHandler);
772
-        APP.connection = connection = con;
773
-
774 627
         this._createRoom(tracks);
775 628
 
776 629
         // if user didn't give access to mic or camera or doesn't have
@@ -860,17 +713,6 @@ export default {
860 713
         };
861 714
 
862 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 716
             APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
875 717
 
876 718
             const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
@@ -897,41 +739,26 @@ export default {
897 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,9 +1240,7 @@ export default {
1413 1240
      * Used by the Breakout Rooms feature to join a breakout room or go back to the main room.
1414 1241
      */
1415 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 1245
         // Restore initial state.
1421 1246
         this._localTracksInitialized = false;
@@ -1440,7 +1265,7 @@ export default {
1440 1265
     },
1441 1266
 
1442 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 1270
         // Filter out the tracks that are muted (except on Safari).
1446 1271
         const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
@@ -1787,10 +1612,10 @@ export default {
1787 1612
             titleKey = 'notify.screenShareNoAudioTitle';
1788 1613
         }
1789 1614
 
1790
-        APP.UI.messageHandler.showError({
1615
+        APP.store.dispatch(showErrorNotification({
1791 1616
             descriptionKey,
1792 1617
             titleKey
1793
-        });
1618
+        }, NOTIFICATION_TIMEOUT_TYPE.LONG));
1794 1619
     },
1795 1620
 
1796 1621
     /**
@@ -2184,21 +2009,6 @@ export default {
2184 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 2012
         APP.UI.addListener(
2203 2013
             UIEvents.VIDEO_DEVICE_CHANGED,
2204 2014
             cameraDeviceId => {

+ 0
- 209
connection.js Visa fil

@@ -1,209 +0,0 @@
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 Visa fil

@@ -370,8 +370,6 @@
370 370
         "permissionCameraRequiredError": "Camera permission is required to participate in conferences with video. Please grant it in Settings",
371 371
         "permissionErrorTitle": "Permission required",
372 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 373
         "readMore": "more",
376 374
         "recentlyUsedObjects": "Your recently used objects",
377 375
         "recording": "Recording",
@@ -439,6 +437,7 @@
439 437
         "token": "token",
440 438
         "tokenAuthFailed": "Sorry, you're not allowed to join this call.",
441 439
         "tokenAuthFailedTitle": "Authentication failed",
440
+        "tokenAuthUnsupported": "Token URL is not supported.",
442 441
         "transcribing": "Transcribing",
443 442
         "unlockRoom": "Remove meeting $t(lockRoomPassword)",
444 443
         "user": "User",

+ 4
- 78
modules/UI/UI.js Visa fil

@@ -6,6 +6,9 @@ const UI = {};
6 6
 import Logger from '@jitsi/logger';
7 7
 import EventEmitter from 'events';
8 8
 
9
+import {
10
+    conferenceWillInit
11
+} from '../../react/features/base/conference/actions';
9 12
 import { isMobileBrowser } from '../../react/features/base/environment/utils';
10 13
 import { setColorAlpha } from '../../react/features/base/util/helpers';
11 14
 import { setDocumentUrl } from '../../react/features/etherpad/actions';
@@ -24,14 +27,11 @@ import {
24 27
 import UIEvents from '../../service/UI/UIEvents';
25 28
 
26 29
 import EtherpadManager from './etherpad/Etherpad';
27
-import messageHandler from './util/MessageHandler';
28 30
 import UIUtil from './util/UIUtil';
29 31
 import VideoLayout from './videolayout/VideoLayout';
30 32
 
31 33
 const logger = Logger.getLogger(__filename);
32 34
 
33
-UI.messageHandler = messageHandler;
34
-
35 35
 const eventEmitter = new EventEmitter();
36 36
 
37 37
 UI.eventEmitter = eventEmitter;
@@ -58,30 +58,6 @@ UI.isFullScreen = function() {
58 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 62
  * Initialize conference UI.
87 63
  */
@@ -91,18 +67,9 @@ UI.initConference = function() {
91 67
 
92 68
 /**
93 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 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 74
     if (isMobileBrowser()) {
108 75
         document.body.classList.add('mobile-browser');
@@ -292,40 +259,6 @@ UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
292 259
 // Used by torture.
293 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 262
 UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
330 263
     VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
331 264
 };
@@ -337,13 +270,6 @@ UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
337 270
  */
338 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 274
  * Update list of available physical devices.
349 275
  */

+ 0
- 227
modules/UI/authentication/AuthHandler.js Visa fil

@@ -1,227 +0,0 @@
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 Visa fil

@@ -1,28 +0,0 @@
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 Visa fil

@@ -1,61 +0,0 @@
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 Visa fil

@@ -31,18 +31,6 @@ const UIUtil = {
31 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 35
      * Indicates if we're currently in full screen mode.
48 36
      *

+ 1
- 0
react/features/app/middlewares.any.ts Visa fil

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

+ 0
- 1
react/features/app/middlewares.native.ts Visa fil

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

+ 1
- 1
react/features/app/middlewares.web.ts Visa fil

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

+ 18
- 0
react/features/authentication/actionTypes.ts Visa fil

@@ -8,6 +8,24 @@
8 8
  */
9 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 30
  * The type of (redux) action which signals that the cyclic operation of waiting
13 31
  * for conference owner has been aborted.

+ 11
- 1
react/features/authentication/actions.any.ts Visa fil

@@ -1,7 +1,7 @@
1 1
 import { IStore } from '../app/types';
2 2
 import { checkIfCanJoin } from '../base/conference/actions';
3 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 6
 import {
7 7
     STOP_WAIT_FOR_OWNER,
@@ -126,6 +126,16 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
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 140
  * Opens {@link WaitForOnwerDialog}.
131 141
  *

+ 9
- 2
react/features/authentication/actions.native.ts Visa fil

@@ -1,4 +1,4 @@
1
-import { appNavigate } from '../app/actions';
1
+import { appNavigate } from '../app/actions.native';
2 2
 import { IStore } from '../app/types';
3 3
 import { conferenceLeft } from '../base/conference/actions';
4 4
 import { connectionFailed } from '../base/connection/actions.native';
@@ -61,4 +61,11 @@ export function cancelWaitForOwner() {
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 Visa fil

@@ -1,12 +1,11 @@
1 1
 import { maybeRedirectToWelcomePage } from '../app/actions.web';
2 2
 import { IStore } from '../app/types';
3
-import { hideDialog, openDialog } from '../base/dialog/actions';
4 3
 
5 4
 import {
6
-    CANCEL_LOGIN
5
+    CANCEL_LOGIN,
6
+    LOGIN,
7
+    LOGOUT
7 8
 } from './actionTypes';
8
-import LoginDialog from './components/web/LoginDialog';
9
-import WaitForOwnerDialog from './components/web/WaitForOwnerDialog';
10 9
 
11 10
 export * from './actions.any';
12 11
 
@@ -35,31 +34,37 @@ export function cancelWaitForOwner() {
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 Visa fil

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

+ 2
- 9
react/features/authentication/components/web/WaitForOwnerDialog.tsx Visa fil

@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
5 5
 import { IStore } from '../../../app/types';
6 6
 import { translate } from '../../../base/i18n/functions';
7 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 11
  * The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
@@ -16,11 +16,6 @@ interface IProps extends WithTranslation {
16 16
      * Redux store dispatch method.
17 17
      */
18 18
     dispatch: IStore['dispatch'];
19
-
20
-    /**
21
-     * Function to be invoked after click.
22
-     */
23
-    onAuthNow?: Function;
24 19
 }
25 20
 
26 21
 /**
@@ -61,9 +56,7 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
61 56
      * @returns {void}
62 57
      */
63 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 Visa fil

@@ -1,4 +1,3 @@
1
-import { appNavigate } from '../app/actions.native';
2 1
 import { IStore } from '../app/types';
3 2
 import {
4 3
     CONFERENCE_FAILED,
@@ -6,6 +5,7 @@ import {
6 5
     CONFERENCE_LEFT
7 6
 } from '../base/conference/actionTypes';
8 7
 import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
8
+import { hangup } from '../base/connection/actions';
9 9
 import { hideDialog } from '../base/dialog/actions';
10 10
 import { isDialogOpen } from '../base/dialog/functions';
11 11
 import {
@@ -13,19 +13,28 @@ import {
13 13
     JitsiConnectionErrors
14 14
 } from '../base/lib-jitsi-meet';
15 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 21
 import {
18 22
     CANCEL_LOGIN,
23
+    LOGIN,
24
+    LOGOUT,
19 25
     STOP_WAIT_FOR_OWNER,
20 26
     UPGRADE_ROLE_FINISHED,
21 27
     WAIT_FOR_OWNER
22 28
 } from './actionTypes';
23 29
 import {
30
+    hideLoginDialog,
24 31
     openLoginDialog,
25 32
     openWaitForOwnerDialog,
33
+    redirectToDefaultLocation,
26 34
     stopWaitForOwner,
27
-    waitForOwner } from './actions.native';
35
+    waitForOwner } from './actions';
28 36
 import { LoginDialog, WaitForOwnerDialog } from './components';
37
+import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
29 38
 
30 39
 /**
31 40
  * Middleware that captures connection or conference failed errors and controls
@@ -40,7 +49,8 @@ MiddlewareRegistry.register(store => next => action => {
40 49
     switch (action.type) {
41 50
     case CANCEL_LOGIN: {
42 51
         const { dispatch, getState } = store;
43
-        const { thenableWithCancel } = getState()['features/authentication'];
52
+        const state = getState();
53
+        const { thenableWithCancel } = state['features/authentication'];
44 54
 
45 55
         thenableWithCancel?.cancel();
46 56
 
@@ -57,10 +67,8 @@ MiddlewareRegistry.register(store => next => action => {
57 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 72
             const { authRequired, conference } = state['features/base/conference'];
65 73
             const { passwordRequired } = state['features/base/connection'];
66 74
 
@@ -68,7 +76,7 @@ MiddlewareRegistry.register(store => next => action => {
68 76
             // NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
69 77
             // connection auth error.
70 78
             if ((passwordRequired || authRequired) && !conference) {
71
-                dispatch(appNavigate(undefined));
79
+                dispatch(redirectToDefaultLocation());
72 80
             }
73 81
         }
74 82
         break;
@@ -100,7 +108,7 @@ MiddlewareRegistry.register(store => next => action => {
100 108
         if (_isWaitingForOwner(store)) {
101 109
             store.dispatch(stopWaitForOwner());
102 110
         }
103
-        _hideLoginDialog(store);
111
+        store.dispatch(hideLoginDialog());
104 112
         break;
105 113
 
106 114
     case CONFERENCE_LEFT:
@@ -108,18 +116,43 @@ MiddlewareRegistry.register(store => next => action => {
108 116
         break;
109 117
 
110 118
     case CONNECTION_ESTABLISHED:
111
-        _hideLoginDialog(store);
119
+        store.dispatch(hideLoginDialog());
112 120
         break;
113 121
 
114 122
     case CONNECTION_FAILED: {
115 123
         const { error } = action;
124
+        const state = store.getState();
125
+        const { jwt } = state['features/base/jwt'];
116 126
 
117 127
         if (error
118 128
                 && error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
119
-                && typeof error.recoverable === 'undefined') {
129
+                && typeof error.recoverable === 'undefined'
130
+                && !jwt) {
120 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 156
         break;
124 157
     }
125 158
 
@@ -132,7 +165,7 @@ MiddlewareRegistry.register(store => next => action => {
132 165
         const { error, progress } = action;
133 166
 
134 167
         if (!error && progress === 1) {
135
-            _hideLoginDialog(store);
168
+            store.dispatch(hideLoginDialog());
136 169
         }
137 170
         break;
138 171
     }
@@ -144,7 +177,7 @@ MiddlewareRegistry.register(store => next => action => {
144 177
 
145 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 181
         // login dialog every few seconds.
149 182
         isDialogOpen(store, LoginDialog)
150 183
             || store.dispatch(openWaitForOwnerDialog());
@@ -162,22 +195,12 @@ MiddlewareRegistry.register(store => next => action => {
162 195
  * @param {Object} store - The redux store.
163 196
  * @returns {void}
164 197
  */
165
-function _clearExistingWaitForOwnerTimeout(
166
-        { getState }: IStore) {
198
+function _clearExistingWaitForOwnerTimeout({ getState }: IStore) {
167 199
     const { waitForOwnerTimeoutID } = getState()['features/authentication'];
168 200
 
169 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 206
  * Checks if the cyclic "wait for conference owner" task is currently scheduled.
@@ -188,3 +211,35 @@ function _hideLoginDialog({ dispatch }: IStore) {
188 211
 function _isWaitingForOwner({ getState }: IStore) {
189 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 Visa fil

@@ -1,152 +0,0 @@
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 Visa fil

@@ -106,6 +106,15 @@ export const CONFERENCE_UNIQUE_ID_SET = 'CONFERENCE_UNIQUE_ID_SET';
106 106
  */
107 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 119
  * The type of (redux) action which signals that a specific conference will be
111 120
  * joined.

+ 16
- 12
react/features/base/conference/actions.ts Visa fil

@@ -1,11 +1,10 @@
1 1
 import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
2 2
 import { sendAnalytics } from '../../analytics/functions';
3
-import { appNavigate } from '../../app/actions';
4 3
 import { IReduxState, IStore } from '../../app/types';
5 4
 import { endpointMessageReceived } from '../../subtitles/actions.any';
6 5
 import { iAmVisitor } from '../../visitors/functions';
7 6
 import { getReplaceParticipant } from '../config/functions';
8
-import { disconnect } from '../connection/actions';
7
+import { hangup } from '../connection/actions';
9 8
 import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
10 9
 import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
11 10
 import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
@@ -41,6 +40,7 @@ import {
41 40
     CONFERENCE_SUBJECT_CHANGED,
42 41
     CONFERENCE_TIMESTAMP_CHANGED,
43 42
     CONFERENCE_UNIQUE_ID_SET,
43
+    CONFERENCE_WILL_INIT,
44 44
     CONFERENCE_WILL_JOIN,
45 45
     CONFERENCE_WILL_LEAVE,
46 46
     DATA_CHANNEL_CLOSED,
@@ -463,6 +463,19 @@ export function _conferenceWillJoin(conference: IJitsiConference) {
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 480
  * Signals the intention of the application to have the local participant
468 481
  * join the specified conference.
@@ -647,18 +660,9 @@ export function kickedOut(conference: IJitsiConference, participant: Object) {
647 660
  * @returns {Function}
648 661
  */
649 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 667
  * Signals that the lock state of a specific JitsiConference changed.
664 668
  *

+ 86
- 84
react/features/base/conference/middleware.any.ts Visa fil

@@ -49,6 +49,7 @@ import {
49 49
 } from './actionTypes';
50 50
 import {
51 51
     conferenceFailed,
52
+    conferenceWillInit,
52 53
     conferenceWillLeave,
53 54
     createConference,
54 55
     leaveConference,
@@ -144,12 +145,6 @@ MiddlewareRegistry.register(store => next => action => {
144 145
 function _conferenceFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
145 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 148
     const result = next(action);
154 149
     const { enableForcedReload } = getState()['features/base/config'];
155 150
 
@@ -197,18 +192,22 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
197 192
         break;
198 193
     }
199 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 213
         break;
@@ -216,45 +215,44 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
216 215
     case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
217 216
         sendAnalytics(createOfferAnswerFailedEvent());
218 217
         break;
218
+
219 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 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 256
         _removeUnloadHandler(getState);
259 257
     }
260 258
 
@@ -339,12 +337,16 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
339 337
  * @private
340 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 341
     const result = next(action);
344 342
 
345 343
     // FIXME: Workaround for the web version. Currently, the creation of the
346 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 351
     return result;
350 352
 }
@@ -386,45 +388,45 @@ function _logJwtErrors(message: string, state: IReduxState) {
386 388
 function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
387 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 396
     const result = next(action);
390 397
 
391 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 431
     return result;
430 432
 }

+ 2
- 1
react/features/base/conference/middleware.web.ts Visa fil

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

+ 3
- 0
react/features/base/conference/reducer.ts Visa fil

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

+ 198
- 1
react/features/base/connection/actions.any.ts Visa fil

@@ -1,6 +1,10 @@
1 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 8
 import {
5 9
     appendURLParam,
6 10
     getBackendSafeRoomName
@@ -10,8 +14,10 @@ import {
10 14
     CONNECTION_DISCONNECTED,
11 15
     CONNECTION_ESTABLISHED,
12 16
     CONNECTION_FAILED,
17
+    CONNECTION_WILL_CONNECT,
13 18
     SET_LOCATION_URL
14 19
 } from './actionTypes';
20
+import { JITSI_CONNECTION_URL_KEY } from './constants';
15 21
 import logger from './logger';
16 22
 
17 23
 /**
@@ -194,3 +200,194 @@ export function setLocationURL(locationURL?: URL) {
194 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 Visa fil

@@ -1,19 +1,7 @@
1
+import { appNavigate } from '../../app/actions.native';
1 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 6
 export * from './actions.any';
19 7
 
@@ -25,178 +13,16 @@ export * from './actions.any';
25 13
  * @returns {Function}
26 14
  */
27 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 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 Visa fil

@@ -1,52 +1,67 @@
1
+// @ts-expect-error
2
+import { jitsiLocalStorage } from '@jitsi/js-utils';
3
+
1 4
 import { IStore } from '../../app/types';
5
+import { getCustomerDetails } from '../../jaas/actions.any';
6
+import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions';
2 7
 import { showWarningNotification } from '../../notifications/actions';
3 8
 import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
4 9
 import { stopLocalVideoRecording } from '../../recording/actions.any';
5 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 15
 export * from './actions.any';
18 16
 
19 17
 /**
20 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 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 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 60
  * request for call feedback.
45 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 65
     return async (dispatch: IStore['dispatch']) => {
51 66
         if (LocalRecordingManager.isRecordingLocally()) {
52 67
             dispatch(stopLocalVideoRecording());

+ 30
- 0
react/features/base/connection/middleware.web.ts Visa fil

@@ -0,0 +1,30 @@
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 Visa fil

@@ -142,7 +142,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
142 142
         const promises = [];
143 143
 
144 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 146
         // is significant and that's where the function createLocalTracksF got
147 147
         // born. I started with the idea a porting so that we could inherit the
148 148
         // ability to getUserMedia for audio only or video only if getUserMedia

+ 24
- 0
react/features/conference/actions.web.ts Visa fil

@@ -1,9 +1,12 @@
1 1
 import { IStore } from '../app/types';
2
+import { configureInitialDevices } from '../base/devices/actions.web';
2 3
 import { getParticipantDisplayName } from '../base/participants/functions';
4
+import { getBackendSafeRoomName } from '../base/util/uri';
3 5
 import { showNotification } from '../notifications/actions';
4 6
 import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
5 7
 
6 8
 import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
9
+import logger from './logger';
7 10
 
8 11
 /**
9 12
  * Notify that we've been kicked out of the conference.
@@ -45,3 +48,24 @@ export function dismissCalendarNotification() {
45 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 Visa fil

@@ -7,7 +7,7 @@ import { connect as reactReduxConnect } from 'react-redux';
7 7
 import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
8 8
 import { IReduxState, IStore } from '../../../app/types';
9 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 11
 import { isMobileBrowser } from '../../../base/environment/utils';
12 12
 import { translate } from '../../../base/i18n/functions';
13 13
 import { setColorAlpha } from '../../../base/util/helpers';
@@ -29,6 +29,7 @@ import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
29 29
 import Toolbox from '../../../toolbox/components/web/Toolbox';
30 30
 import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
31 31
 import { getCurrentLayout } from '../../../video-layout/functions.any';
32
+import { init } from '../../actions.web';
32 33
 import { maybeShowSuboptimalExperienceNotification } from '../../functions.web';
33 34
 import {
34 35
     AbstractConference,
@@ -187,7 +188,7 @@ class Conference extends AbstractConference<IProps, any> {
187 188
         FULL_SCREEN_EVENTS.forEach(name =>
188 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,7 +375,7 @@ class Conference extends AbstractConference<IProps, any> {
374 375
 
375 376
         const { dispatch, t } = this.props;
376 377
 
377
-        dispatch(connect());
378
+        dispatch(init());
378 379
 
379 380
         maybeShowSuboptimalExperienceNotification(dispatch, t);
380 381
     }

+ 14
- 0
react/features/prejoin/actions.any.ts Visa fil

@@ -0,0 +1,14 @@
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 Visa fil

@@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
3 3
 import { IStore } from '../app/types';
4 4
 import { updateConfig } from '../base/config/actions';
5 5
 import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
6
+import { connect } from '../base/connection/actions';
6 7
 import { browser } from '../base/lib-jitsi-meet';
7 8
 import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
8 9
 import { MEDIA_TYPE } from '../base/media/constants';
@@ -31,7 +32,6 @@ import {
31 32
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
32 33
     SET_PRECALL_TEST_RESULTS,
33 34
     SET_PREJOIN_DEVICE_ERRORS,
34
-    SET_PREJOIN_DISPLAY_NAME_REQUIRED,
35 35
     SET_PREJOIN_PAGE_VISIBILITY,
36 36
     SET_SKIP_PREJOIN_RELOAD
37 37
 } from './actionTypes';
@@ -66,6 +66,8 @@ const STATUS_REQ_FREQUENCY = 2000;
66 66
  */
67 67
 const STATUS_REQ_CAP = 45;
68 68
 
69
+export * from './actions.any';
70
+
69 71
 /**
70 72
  * Polls for status change after dial out.
71 73
  * Changes dialog message based on response, closes the dialog if there is an error,
@@ -226,33 +228,37 @@ export function joinConference(options?: Object, ignoreJoiningInProgress = false
226 228
             dispatch(setJoiningInProgress(true));
227 229
         }
228 230
 
229
-        const state = getState();
230
-        let localTracks = getLocalTracks(state['features/base/tracks']);
231
-
232 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,17 +471,6 @@ export function setDialOutCountry(value: Object) {
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 475
  * Action used to set the dial out number.
481 476
  *

+ 3
- 3
react/features/room-lock/middleware.ts Visa fil

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

+ 10
- 0
react/features/settings/actions.native.ts Visa fil

@@ -0,0 +1,10 @@
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 Visa fil

@@ -2,11 +2,12 @@ import { Theme } from '@mui/material';
2 2
 import { withStyles } from '@mui/styles';
3 3
 import React from 'react';
4 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 7
 import { createProfilePanelButtonEvent } from '../../../analytics/AnalyticsEvents';
9 8
 import { sendAnalytics } from '../../../analytics/functions';
9
+import { IStore } from '../../../app/types';
10
+import { login, logout } from '../../../authentication/actions.web';
10 11
 import Avatar from '../../../base/avatar/components/Avatar';
11 12
 import AbstractDialogTab, {
12 13
     IProps as AbstractDialogTabProps } from '../../../base/dialog/components/web/AbstractDialogTab';
@@ -14,7 +15,6 @@ import { translate } from '../../../base/i18n/functions';
14 15
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
15 16
 import Button from '../../../base/ui/components/web/Button';
16 17
 import Input from '../../../base/ui/components/web/Input';
17
-import { openLogoutDialog } from '../../actions';
18 18
 
19 19
 /**
20 20
  * The type of the React {@code Component} props of {@link ProfileTab}.
@@ -22,7 +22,7 @@ import { openLogoutDialog } from '../../actions';
22 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 27
     authEnabled: boolean;
28 28
 
@@ -36,6 +36,11 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
36 36
      */
37 37
     classes: any;
38 38
 
39
+    /**
40
+     * Invoked to change the configured calendar integration.
41
+     */
42
+    dispatch: IStore['dispatch'];
43
+
39 44
     /**
40 45
      * The display name to display for the local participant.
41 46
      */
@@ -204,13 +209,11 @@ class ProfileTab extends AbstractDialogTab<IProps, any> {
204 209
         if (this.props.authLogin) {
205 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 213
         } else {
211 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,4 +249,4 @@ class ProfileTab extends AbstractDialogTab<IProps, any> {
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 Visa fil

@@ -1,6 +1,6 @@
1 1
 // @ts-expect-error
2 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 4
 import { MEDIA_TYPE } from '../base/media/constants';
5 5
 import { PARTICIPANT_JOINED } from '../base/participants/actionTypes';
6 6
 import { getLocalParticipant } from '../base/participants/functions';
@@ -25,6 +25,11 @@ MiddlewareRegistry.register(store => next => action => {
25 25
     const result = next(action);
26 26
 
27 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 33
     case CONFERENCE_WILL_LEAVE:
29 34
         VideoLayout.reset();
30 35
         break;

+ 0
- 2
service/UI/UIEvents.js Visa fil

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

Laddar…
Avbryt
Spara