Преглед изворни кода

core: refactor routing

Unfortunately, as the Jitsi Meet development evolved the routing mechanism
became more complex and thre logic ended up spread across multiple parts of the
codebase, which made it hard to follow and extend.

This change aims to fix that by rewriting the routing logic and centralizing it
in (pretty much) a single place, with no implicit inter-dependencies.

In order to arrive there, however, some extra changes were needed, which were
not caught early enough and are thus part of this change:

- JitsiMeetJS initialization is now synchronous: there is nothing async about
  it, and the only async requirement (Temasys support) was lifted. See [0].
- WebRTC support can be detected early: building on top of the above, WebRTC
  support can now be detected immediately, so take advantage of this to simplify
  how we handle unsupported browsers. See [0].

The new router takes decissions based on the Redux state at the time of
invocation. A route can be represented by either a component or a URl reference,
with the latter taking precedence. On mobile, obviously, there is no concept of
URL reference so routing is based solely on components.

[0]: https://github.com/jitsi/lib-jitsi-meet/pull/779
master
Saúl Ibarra Corretgé пре 7 година
родитељ
комит
155e02bbfb
38 измењених фајлова са 343 додато и 912 уклоњено
  1. 30
    36
      conference.js
  2. 0
    32
      modules/URL/ConferenceUrl.js
  3. 8
    15
      react/features/app/actions.js
  4. 10
    47
      react/features/app/components/AbstractApp.js
  5. 0
    97
      react/features/app/components/App.web.js
  6. 1
    1
      react/features/app/functions.native.js
  7. 2
    82
      react/features/app/functions.web.js
  8. 0
    22
      react/features/app/getRouteToRender.js
  9. 3
    39
      react/features/app/middleware.js
  10. 109
    0
      react/features/app/router.js
  11. 4
    1
      react/features/base/conference/reducer.js
  12. 2
    14
      react/features/base/connection/actions.web.js
  13. 0
    20
      react/features/base/lib-jitsi-meet/actionTypes.js
  14. 11
    47
      react/features/base/lib-jitsi-meet/actions.js
  15. 0
    5
      react/features/base/lib-jitsi-meet/constants.js
  16. 0
    1
      react/features/base/lib-jitsi-meet/index.js
  17. 2
    41
      react/features/base/lib-jitsi-meet/middleware.js
  18. 2
    17
      react/features/base/lib-jitsi-meet/reducer.js
  19. 0
    119
      react/features/base/react/RouteRegistry.js
  20. 0
    1
      react/features/base/react/index.js
  21. 57
    4
      react/features/conference/components/Conference.native.js
  22. 85
    20
      react/features/conference/components/Conference.web.js
  23. 0
    1
      react/features/conference/index.js
  24. 0
    105
      react/features/conference/route.js
  25. 0
    0
      react/features/deep-linking/components/DeepLinkingDesktopPage.native.js
  26. 0
    0
      react/features/deep-linking/components/DeepLinkingDesktopPage.web.js
  27. 0
    0
      react/features/deep-linking/components/DeepLinkingMobilePage.native.js
  28. 0
    0
      react/features/deep-linking/components/DeepLinkingMobilePage.web.js
  29. 0
    0
      react/features/deep-linking/components/NoMobileApp.native.js
  30. 0
    0
      react/features/deep-linking/components/NoMobileApp.web.js
  31. 1
    4
      react/features/device-selection/actions.js
  32. 2
    3
      react/features/settings/popup.js
  33. 0
    2
      react/features/unsupported-browser/index.js
  34. 0
    54
      react/features/unsupported-browser/middleware.js
  35. 12
    40
      react/features/welcome/components/BlankPage.native.js
  36. 2
    6
      react/features/welcome/functions.js
  37. 0
    1
      react/features/welcome/index.js
  38. 0
    35
      react/features/welcome/route.js

+ 30
- 36
conference.js Прегледај датотеку

2320
      * @private
2320
      * @private
2321
      */
2321
      */
2322
     _initDeviceList() {
2322
     _initDeviceList() {
2323
-        JitsiMeetJS.mediaDevices.isDeviceListAvailable()
2324
-            .then(isDeviceListAvailable => {
2325
-                if (isDeviceListAvailable
2326
-                        && JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
2327
-                    JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
2328
-                        // Ugly way to synchronize real device IDs with local
2329
-                        // storage and settings menu. This is a workaround until
2330
-                        // getConstraints() method will be implemented
2331
-                        // in browsers.
2332
-                        const { dispatch } = APP.store;
2333
-
2334
-                        if (this.localAudio) {
2335
-                            dispatch(updateSettings({
2336
-                                micDeviceId: this.localAudio.getDeviceId()
2337
-                            }));
2338
-                        }
2339
-
2340
-                        if (this.localVideo) {
2341
-                            dispatch(updateSettings({
2342
-                                cameraDeviceId: this.localVideo.getDeviceId()
2343
-                            }));
2344
-                        }
2345
-
2346
-                        mediaDeviceHelper.setCurrentMediaDevices(devices);
2347
-                        APP.UI.onAvailableDevicesChanged(devices);
2348
-                        APP.store.dispatch(updateDeviceList(devices));
2349
-                    });
2323
+        if (JitsiMeetJS.mediaDevices.isDeviceListAvailable()
2324
+                && JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
2325
+            JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
2326
+                // Ugly way to synchronize real device IDs with local
2327
+                // storage and settings menu. This is a workaround until
2328
+                // getConstraints() method will be implemented
2329
+                // in browsers.
2330
+                const { dispatch } = APP.store;
2331
+
2332
+                if (this.localAudio) {
2333
+                    dispatch(updateSettings({
2334
+                        micDeviceId: this.localAudio.getDeviceId()
2335
+                    }));
2336
+                }
2350
 
2337
 
2351
-                    this.deviceChangeListener = devices =>
2352
-                        window.setTimeout(
2353
-                            () => this._onDeviceListChanged(devices), 0);
2354
-                    JitsiMeetJS.mediaDevices.addEventListener(
2355
-                        JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
2356
-                        this.deviceChangeListener);
2338
+                if (this.localVideo) {
2339
+                    dispatch(updateSettings({
2340
+                        cameraDeviceId: this.localVideo.getDeviceId()
2341
+                    }));
2357
                 }
2342
                 }
2358
-            })
2359
-            .catch(error => {
2360
-                logger.warn(`Error getting device list: ${error}`);
2343
+
2344
+                mediaDeviceHelper.setCurrentMediaDevices(devices);
2345
+                APP.UI.onAvailableDevicesChanged(devices);
2346
+                APP.store.dispatch(updateDeviceList(devices));
2361
             });
2347
             });
2348
+
2349
+            this.deviceChangeListener = devices =>
2350
+                window.setTimeout(
2351
+                    () => this._onDeviceListChanged(devices), 0);
2352
+            JitsiMeetJS.mediaDevices.addEventListener(
2353
+                JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
2354
+                this.deviceChangeListener);
2355
+        }
2362
     },
2356
     },
2363
 
2357
 
2364
     /**
2358
     /**

+ 0
- 32
modules/URL/ConferenceUrl.js Прегледај датотеку

1
-const logger = require('jitsi-meet-logger').getLogger(__filename);
2
-
3
-/**
4
- * The modules stores information about the URL used to start the conference and
5
- * provides utility methods for dealing with conference URL and reloads.
6
- */
7
-export default class ConferenceUrl {
8
-    /**
9
-     * Initializes the module.
10
-     *
11
-     * @param location an object which stores provides the info about conference
12
-     * URL(would be 'window.location' for the Web app). The params below are
13
-     * described based on the following example URL:
14
-     *
15
-     * https://example.com:8888/SomeConference1245?opt=1#somehash
16
-     *
17
-     * @param location.href full URL with all parameters, would be the whole URL
18
-     * from the example string above.
19
-     * @param location.host the host part of the URL, 'example.com' from
20
-     * the sample URL above.
21
-     * @param location.pathname the path part of the URL, would be
22
-     * '/SomeConference1245' from the example above.
23
-     * @param location.protocol the protocol part of the URL, would be 'https:'
24
-     * from the sample URL.
25
-     */
26
-    constructor(location) {
27
-        logger.info(`Stored original conference URL: ${location.href}`);
28
-        logger.info(
29
-                `Conference URL for invites: ${location.protocol}//${
30
-                    location.host}${location.pathname}`);
31
-    }
32
-}

+ 8
- 15
react/features/app/actions.js Прегледај датотеку

77
      * @returns {void}
77
      * @returns {void}
78
      */
78
      */
79
     function loadConfigSettled(error, config) {
79
     function loadConfigSettled(error, config) {
80
-        let promise;
81
-
82
         // Due to the asynchronous nature of the loading, the specified config
80
         // Due to the asynchronous nature of the loading, the specified config
83
         // may or may not be required by the time the notification arrives.
81
         // may or may not be required by the time the notification arrives.
84
         // If we receive the config for a location we are no longer interested
82
         // If we receive the config for a location we are no longer interested
85
         // in, "ignore" it - deliver it to the external API, for example, but do
83
         // in, "ignore" it - deliver it to the external API, for example, but do
86
         // not proceed with the appNavigate procedure/process.
84
         // not proceed with the appNavigate procedure/process.
87
         if (getState()['features/base/config'].locationURL === locationURL) {
85
         if (getState()['features/base/config'].locationURL === locationURL) {
88
-            promise = dispatch(setLocationURL(locationURL));
86
+            dispatch(setLocationURL(locationURL));
87
+            dispatch(setConfig(config));
89
         } else {
88
         } else {
90
             // eslint-disable-next-line no-param-reassign
89
             // eslint-disable-next-line no-param-reassign
91
             error || (error = new Error('Config no longer needed!'));
90
             error || (error = new Error('Config no longer needed!'));
92
-            promise = Promise.resolve();
93
-        }
94
 
91
 
95
-        return promise.then(() => {
96
-            if (error) {
97
-                // XXX The failure could be, for example, because of a
98
-                // certificate-related error. In which case the connection will
99
-                // fail later in Strophe anyway.
100
-                dispatch(loadConfigError(error, locationURL));
101
-                throw error;
102
-            }
92
+            // XXX The failure could be, for example, because of a
93
+            // certificate-related error. In which case the connection will
94
+            // fail later in Strophe anyway.
95
+            dispatch(loadConfigError(error, locationURL));
103
 
96
 
104
-            return dispatch(setConfig(config));
105
-        });
97
+            throw error;
98
+        }
106
     }
99
     }
107
 }
100
 }
108
 
101
 

+ 10
- 47
react/features/app/components/AbstractApp.js Прегледај датотеку

1
 /* global APP */
1
 /* global APP */
2
 
2
 
3
+import _ from 'lodash';
3
 import PropTypes from 'prop-types';
4
 import PropTypes from 'prop-types';
4
 import React, { Component, Fragment } from 'react';
5
 import React, { Component, Fragment } from 'react';
5
 import { I18nextProvider } from 'react-i18next';
6
 import { I18nextProvider } from 'react-i18next';
9
 
10
 
10
 import { i18next } from '../../base/i18n';
11
 import { i18next } from '../../base/i18n';
11
 import { localParticipantLeft } from '../../base/participants';
12
 import { localParticipantLeft } from '../../base/participants';
12
-import { RouteRegistry } from '../../base/react';
13
 import {
13
 import {
14
     MiddlewareRegistry,
14
     MiddlewareRegistry,
15
     ReducerRegistry,
15
     ReducerRegistry,
19
 import { PersistenceRegistry } from '../../base/storage';
19
 import { PersistenceRegistry } from '../../base/storage';
20
 import { toURLString } from '../../base/util';
20
 import { toURLString } from '../../base/util';
21
 import { OverlayContainer } from '../../overlay';
21
 import { OverlayContainer } from '../../overlay';
22
-import { BlankPage } from '../../welcome';
23
 
22
 
24
 import { appNavigate, appWillMount, appWillUnmount } from '../actions';
23
 import { appNavigate, appWillMount, appWillUnmount } from '../actions';
25
 
24
 
84
              *
83
              *
85
              * @type {Route}
84
              * @type {Route}
86
              */
85
              */
87
-            route: undefined,
86
+            route: {},
88
 
87
 
89
             /**
88
             /**
90
              * The state of the »possible« async initialization of
89
              * The state of the »possible« async initialization of
114
                 .catch(() => { /* AbstractApp should always initialize! */ })
113
                 .catch(() => { /* AbstractApp should always initialize! */ })
115
                 .then(() =>
114
                 .then(() =>
116
                     this.setState({
115
                     this.setState({
117
-                        route: undefined,
118
                         store: this._maybeCreateStore(props)
116
                         store: this._maybeCreateStore(props)
119
                     }));
117
                     }));
120
     }
118
     }
242
      */
240
      */
243
     render() {
241
     render() {
244
         const { appAsyncInitialized, route } = this.state;
242
         const { appAsyncInitialized, route } = this.state;
245
-        const component = (route && route.component) || BlankPage;
243
+        const { component } = route;
246
 
244
 
247
         if (appAsyncInitialized && component) {
245
         if (appAsyncInitialized && component) {
248
             return (
246
             return (
421
      * @returns {Promise}
419
      * @returns {Promise}
422
      */
420
      */
423
     _navigate(route) {
421
     _navigate(route) {
424
-        if (RouteRegistry.areRoutesEqual(this.state.route, route)) {
422
+        if (_.isEqual(route, this.state.route)) {
425
             return Promise.resolve();
423
             return Promise.resolve();
426
         }
424
         }
427
 
425
 
428
-        let nextState = {
429
-            route
430
-        };
426
+        if (route.href) {
427
+            // This navigation requires loading a new URL in the browser.
428
+            window.location.href = route.href;
431
 
429
 
432
-        // The Web App was using react-router so it utilized react-router's
433
-        // onEnter. During the removal of react-router, modifications were
434
-        // minimized by preserving the onEnter interface:
435
-        // (1) Router would provide its nextState to the Route's onEnter. As the
436
-        // role of Router is now this AbstractApp and we use redux, provide the
437
-        // redux store instead.
438
-        // (2) A replace function would be provided to the Route in case it
439
-        // chose to redirect to another path.
440
-        route && this._onRouteEnter(route, this._getStore(), pathname => {
441
-            if (pathname) {
442
-                this._openURL(pathname);
443
-
444
-                // Do not proceed with the route because it chose to redirect to
445
-                // another path.
446
-                nextState = undefined;
447
-            } else {
448
-                nextState.route = undefined;
449
-            }
450
-        });
430
+            return Promise.resolve();
431
+        }
451
 
432
 
452
         // XXX React's setState is asynchronous which means that the value of
433
         // XXX React's setState is asynchronous which means that the value of
453
         // this.state.route above may not even be correct. If the check is
434
         // this.state.route above may not even be correct. If the check is
455
         // expected route. In order to mitigate the problem, _navigate was
436
         // expected route. In order to mitigate the problem, _navigate was
456
         // changed to return a Promise.
437
         // changed to return a Promise.
457
         return new Promise(resolve => {
438
         return new Promise(resolve => {
458
-            if (nextState) {
459
-                this.setState(nextState, resolve);
460
-            } else {
461
-                resolve();
462
-            }
439
+            this.setState({ route }, resolve);
463
         });
440
         });
464
     }
441
     }
465
 
442
 
466
-    /**
467
-     * Notifies this {@code App} that a specific Route is about to be rendered.
468
-     *
469
-     * @param {Route} route - The Route that is about to be rendered.
470
-     * @private
471
-     * @returns {void}
472
-     */
473
-    _onRouteEnter(route, ...args) {
474
-        // Notify the route that it is about to be entered.
475
-        const { onEnter } = route;
476
-
477
-        typeof onEnter === 'function' && onEnter(...args);
478
-    }
479
-
480
     /**
443
     /**
481
      * Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
444
      * Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
482
      *
445
      *

+ 0
- 97
react/features/app/components/App.web.js Прегледај датотеку

2
 import React from 'react';
2
 import React from 'react';
3
 
3
 
4
 import '../../base/responsive-ui';
4
 import '../../base/responsive-ui';
5
-import { getLocationContextRoot } from '../../base/util';
6
 import '../../chat';
5
 import '../../chat';
7
 import '../../room-lock';
6
 import '../../room-lock';
8
 import '../../video-layout';
7
 import '../../video-layout';
22
      */
21
      */
23
     static propTypes = AbstractApp.propTypes;
22
     static propTypes = AbstractApp.propTypes;
24
 
23
 
25
-    /**
26
-     * Initializes a new App instance.
27
-     *
28
-     * @param {Object} props - The read-only React Component props with which
29
-     * the new instance is to be initialized.
30
-     */
31
-    constructor(props) {
32
-        super(props);
33
-
34
-        this.state = {
35
-            ...this.state,
36
-
37
-            /**
38
-             * The context root of window.location i.e. this Web App.
39
-             *
40
-             * @type {string}
41
-             */
42
-            windowLocationContextRoot: this._getWindowLocationContextRoot()
43
-        };
44
-    }
45
-
46
     /**
24
     /**
47
      * Overrides the parent method to inject {@link AtlasKitThemeProvider} as
25
      * Overrides the parent method to inject {@link AtlasKitThemeProvider} as
48
      * the top most component.
26
      * the top most component.
66
     getWindowLocation() {
44
     getWindowLocation() {
67
         return window.location;
45
         return window.location;
68
     }
46
     }
69
-
70
-    /**
71
-     * Gets the context root of this Web App from window.location.
72
-     *
73
-     * @private
74
-     * @returns {string} The context root of window.location i.e. this Web App.
75
-     */
76
-    _getWindowLocationContextRoot() {
77
-        return getLocationContextRoot(this.getWindowLocation());
78
-    }
79
-
80
-    /**
81
-     * Navigates to a specific Route (via platform-specific means).
82
-     *
83
-     * @param {Route} route - The Route to which to navigate.
84
-     * @protected
85
-     * @returns {void}
86
-     */
87
-    _navigate(route) {
88
-        let path;
89
-
90
-        if (route) {
91
-            path = route.path;
92
-
93
-            const store = this._getStore();
94
-
95
-            // The syntax :room bellow is defined by react-router. It "matches a
96
-            // URL segment up to the next /, ?, or #. The matched string is
97
-            // called a param."
98
-            path
99
-                = path.replace(
100
-                    /:room/g,
101
-                    store.getState()['features/base/conference'].room);
102
-            path = this._routePath2WindowLocationPathname(path);
103
-        }
104
-
105
-        // Navigate to the specified Route.
106
-        const windowLocation = this.getWindowLocation();
107
-        let promise;
108
-
109
-        if (!route || windowLocation.pathname === path) {
110
-            // The browser is at the specified path already and what remains is
111
-            // to make this App instance aware of the route to be rendered at
112
-            // the current location.
113
-
114
-            // XXX Refer to the super's _navigate for an explanation why a
115
-            // Promise is returned.
116
-            promise = super._navigate(route);
117
-        } else {
118
-            // The browser must go to the specified location. Once the specified
119
-            // location becomes current, the App will be made aware of the route
120
-            // to be rendered at it.
121
-            windowLocation.pathname = path;
122
-        }
123
-
124
-        return promise || Promise.resolve();
125
-    }
126
-
127
-    /**
128
-     * Converts a specific Route path to a window.location.pathname.
129
-     *
130
-     * @param {string} path - A Route path to be converted to/represeted as a
131
-     * window.location.pathname.
132
-     * @private
133
-     * @returns {string} A window.location.pathname-compatible representation of
134
-     * the specified Route path.
135
-     */
136
-    _routePath2WindowLocationPathname(path) {
137
-        let pathname = this.state.windowLocationContextRoot;
138
-
139
-        pathname.endsWith('/') || (pathname += '/');
140
-        pathname += path.startsWith('/') ? path.substring(1) : path;
141
-
142
-        return pathname;
143
-    }
144
 }
47
 }

+ 1
- 1
react/features/app/functions.native.js Прегледај датотеку

3
 import { NativeModules } from 'react-native';
3
 import { NativeModules } from 'react-native';
4
 
4
 
5
 export * from './functions.any';
5
 export * from './functions.any';
6
-export * from './getRouteToRender';
6
+export * from './router';
7
 
7
 
8
 /**
8
 /**
9
  * Returns application name.
9
  * Returns application name.

+ 2
- 82
react/features/app/functions.web.js Прегледај датотеку

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { toState } from '../base/redux';
4
-import { getDeepLinkingPage } from '../deep-linking';
5
-import { UnsupportedDesktopBrowser } from '../unsupported-browser';
6
-
7
-import {
8
-    // eslint-disable-next-line camelcase
9
-    _getRouteToRender as _super_getRouteToRender
10
-} from './getRouteToRender';
11
-
12
-declare var APP: Object;
13
-declare var interfaceConfig: Object;
14
-declare var loggingConfig: Object;
15
-
16
-/**
17
- * Array of rules defining whether we should {@link _interceptComponent} to
18
- * render.
19
- *
20
- * @private
21
- * @param {Object} state - Object containing current redux state.
22
- * @returns {Promise<ReactElement>|void}
23
- * @type {Function[]}
24
- */
25
-const _INTERCEPT_COMPONENT_RULES = [
26
-    getDeepLinkingPage,
27
-    state => {
28
-        const { webRTCReady } = state['features/base/lib-jitsi-meet'];
29
-
30
-        if (webRTCReady === false) {
31
-            return Promise.resolve(UnsupportedDesktopBrowser);
32
-        }
33
-
34
-        return Promise.resolve();
35
-    }
36
-];
37
-
38
 export * from './functions.any';
3
 export * from './functions.any';
4
+export * from './router';
39
 
5
 
40
-/**
41
- * Determines which route is to be rendered in order to depict a specific redux
42
- * store.
43
- *
44
- * @param {(Object|Function)} stateOrGetState - The redux state or
45
- * {@link getState} function.
46
- * @returns {Promise<Route>}
47
- */
48
-export function _getRouteToRender(stateOrGetState: Object | Function): Object {
49
-    const route = _super_getRouteToRender(stateOrGetState);
50
-
51
-    // Intercepts route components if any of component interceptor rules is
52
-    // satisfied.
53
-    return _interceptComponent(stateOrGetState, route.component).then(
54
-        (component: React$Element<*>) => {
55
-            route.component = component;
56
-
57
-            return route;
58
-        }, () => Promise.resolve(route));
59
-}
60
-
61
-/**
62
- * Intercepts route components based on a {@link _INTERCEPT_COMPONENT_RULES}.
63
- *
64
- * @param {Object|Function} stateOrGetState - The redux state or
65
- * {@link getState} function.
66
- * @param {ReactElement} component - Current route component to render.
67
- * @private
68
- * @returns {Promise<ReactElement>} If any of the pre-defined rules is
69
- * satisfied, returns intercepted component.
70
- */
71
-function _interceptComponent(
72
-        stateOrGetState: Object | Function,
73
-        component: React$Element<*>) {
74
-    const state = toState(stateOrGetState);
75
-
76
-    const promises = [];
77
-
78
-    _INTERCEPT_COMPONENT_RULES.forEach(rule => {
79
-        promises.push(rule(state));
80
-    });
81
-
82
-    return Promise.all(promises).then(
83
-        results =>
84
-            results.find(result => typeof result !== 'undefined') || component,
85
-        () => Promise.resolve(component));
86
-}
6
+declare var interfaceConfig: Object;
87
 
7
 
88
 /**
8
 /**
89
  * Returns application name.
9
  * Returns application name.

+ 0
- 22
react/features/app/getRouteToRender.js Прегледај датотеку

1
-/* @flow */
2
-
3
-import { isRoomValid } from '../base/conference';
4
-import { RouteRegistry } from '../base/react';
5
-import { toState } from '../base/redux';
6
-import { Conference } from '../conference';
7
-import { WelcomePage } from '../welcome';
8
-
9
-/**
10
- * Determines which route is to be rendered in order to depict a specific Redux
11
- * store.
12
- *
13
- * @param {(Object|Function)} stateOrGetState - Redux state or Regux getState()
14
- * method.
15
- * @returns {Route}
16
- */
17
-export function _getRouteToRender(stateOrGetState: Object | Function) {
18
-    const { room } = toState(stateOrGetState)['features/base/conference'];
19
-    const component = isRoomValid(room) ? Conference : WelcomePage;
20
-
21
-    return RouteRegistry.getRouteByComponent(component);
22
-}

+ 3
- 39
react/features/app/middleware.js Прегледај датотеку

3
 import { SET_ROOM } from '../base/conference';
3
 import { SET_ROOM } from '../base/conference';
4
 import {
4
 import {
5
     CONNECTION_ESTABLISHED,
5
     CONNECTION_ESTABLISHED,
6
-    getURLWithoutParams,
7
-    SET_LOCATION_URL
6
+    getURLWithoutParams
8
 } from '../base/connection';
7
 } from '../base/connection';
9
 import { MiddlewareRegistry } from '../base/redux';
8
 import { MiddlewareRegistry } from '../base/redux';
10
 
9
 
11
-import { _getRouteToRender } from './functions';
10
+import { getRouteToRender } from './router';
12
 
11
 
13
 MiddlewareRegistry.register(store => next => action => {
12
 MiddlewareRegistry.register(store => next => action => {
14
     switch (action.type) {
13
     switch (action.type) {
15
     case CONNECTION_ESTABLISHED:
14
     case CONNECTION_ESTABLISHED:
16
         return _connectionEstablished(store, next, action);
15
         return _connectionEstablished(store, next, action);
17
 
16
 
18
-    case SET_LOCATION_URL:
19
-        return _setLocationURL(store, next, action);
20
-
21
     case SET_ROOM:
17
     case SET_ROOM:
22
         return _setRoom(store, next, action);
18
         return _setRoom(store, next, action);
23
     }
19
     }
77
 function _navigate({ getState }) {
73
 function _navigate({ getState }) {
78
     const state = getState();
74
     const state = getState();
79
     const { app } = state['features/app'];
75
     const { app } = state['features/app'];
80
-    const routeToRender = _getRouteToRender(state);
81
-
82
-    // XXX Web changed _getRouteToRender to return Promsie instead of Route.
83
-    // Unfortunately, the commit left mobile to return Route.
84
-    let routeToRenderPromise;
85
-
86
-    if (routeToRender && typeof routeToRender.then === 'function') {
87
-        routeToRenderPromise = routeToRender;
88
-    }
89
-    if (!routeToRenderPromise) {
90
-        routeToRenderPromise = Promise.resolve(routeToRender);
91
-    }
92
 
76
 
93
-    routeToRenderPromise.then(app._navigate.bind(app));
94
-}
95
-
96
-/**
97
- * Notifies the feature app that the action {@link SET_LOCATION_URL} is being
98
- * dispatched within a specific redux {@code store}.
99
- *
100
- * @param {Store} store - The redux store in which the specified {@code action}
101
- * is being dispatched.
102
- * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
103
- * specified {@code action} to the specified {@code store}.
104
- * @param {Action} action - The redux action, {@code SET_LOCATION_URL}, which is
105
- * being dispatched in the specified {@code store}.
106
- * @private
107
- * @returns {Object} The new state that is the result of the reduction of the
108
- * specified {@code action}.
109
- */
110
-function _setLocationURL({ getState }, next, action) {
111
-    return (
112
-        getState()['features/app'].app._navigate(undefined)
113
-            .then(() => next(action)));
77
+    getRouteToRender(state).then(route => app._navigate(route));
114
 }
78
 }
115
 
79
 
116
 /**
80
 /**

+ 109
- 0
react/features/app/router.js Прегледај датотеку

1
+// @flow
2
+import type { Component } from 'react';
3
+
4
+import { isRoomValid } from '../base/conference';
5
+import JitsiMeetJS from '../base/lib-jitsi-meet';
6
+import { Platform } from '../base/react';
7
+import { toState } from '../base/redux';
8
+import { Conference } from '../conference';
9
+import { getDeepLinkingPage } from '../deep-linking';
10
+import { UnsupportedDesktopBrowser } from '../unsupported-browser';
11
+import {
12
+    BlankPage,
13
+    WelcomePage,
14
+    generateRoomWithoutSeparator,
15
+    isWelcomePageAppEnabled,
16
+    isWelcomePageUserEnabled
17
+} from '../welcome';
18
+
19
+declare var interfaceConfig: Object;
20
+
21
+/**
22
+ * Object describing application route.
23
+ *
24
+ * @typedef {Object} Route
25
+ * @property {Component} component - React Component constructor.
26
+ * @property {string|undefined} href - New location, in case navigation involves
27
+ * a location change.
28
+ */
29
+export type Route = {
30
+    component: Class<Component<*>>,
31
+    href: ?string
32
+};
33
+
34
+/**
35
+ * Determines which route is to be rendered in order to depict a specific Redux
36
+ * store.
37
+ *
38
+ * @param {(Object|Function)} stateful - Redux state or Regux getState()
39
+ * method.
40
+ * @returns {Promise<Route>}
41
+ */
42
+export function getRouteToRender(stateful: Object | Function): Promise<Route> {
43
+    const state = toState(stateful);
44
+    const { room } = state['features/base/conference'];
45
+    const isMobileApp = navigator.product === 'ReactNative';
46
+    const isMobileBrowser
47
+        = !isMobileApp && (Platform.OS === 'android' || Platform.OS === 'ios');
48
+    const route: Route = {
49
+        component: BlankPage,
50
+        href: undefined
51
+    };
52
+
53
+    return new Promise(resolve => {
54
+        // First check if the current endpoint supports WebRTC. We are
55
+        // intentionally not performing the check for mobile browsers because:
56
+        //   - the welcome page is mobile ready
57
+        //   - if the URL points to a conference, getDeepLinkingPage will take
58
+        //     care of it
59
+        if (!isMobileBrowser && !JitsiMeetJS.isWebRtcSupported()) {
60
+            route.component = UnsupportedDesktopBrowser;
61
+            resolve(route);
62
+
63
+            return;
64
+        }
65
+
66
+        if (isRoomValid(room)) {
67
+            if (isMobileApp) {
68
+                route.component = Conference;
69
+                resolve(route);
70
+            } else {
71
+                // Update the location if it doesn't match. This happens when a
72
+                // room is joined from the welcome page. The reason for doing
73
+                // this instead of using the history API is that we want to
74
+                // load the config.js which takes the room into account.
75
+                const { locationURL } = state['features/base/connection'];
76
+
77
+                // eslint-disable-next-line no-negated-condition
78
+                if (window.location.href !== locationURL.href) {
79
+                    route.href = locationURL.href;
80
+                    resolve(route);
81
+                } else {
82
+                    // Maybe show deep-linking, otherwise go to Conference.
83
+                    getDeepLinkingPage(state).then(component => {
84
+                        route.component = component || Conference;
85
+                        resolve(route);
86
+                    });
87
+                }
88
+            }
89
+
90
+            return;
91
+        }
92
+
93
+        if (!isWelcomePageUserEnabled(state)) {
94
+            // Web: if the welcome page is disabled, go directly to a
95
+            // random room.
96
+
97
+            let href = window.location.href;
98
+
99
+            href.endsWith('/') || (href += '/');
100
+            route.href = href + generateRoomWithoutSeparator();
101
+        } else if (isWelcomePageAppEnabled(state)) {
102
+            // Mobile: only go to the welcome page if enabled.
103
+
104
+            route.component = WelcomePage;
105
+        }
106
+
107
+        resolve(route);
108
+    });
109
+}

+ 4
- 1
react/features/base/conference/reducer.js Прегледај датотеку

1
 // @flow
1
 // @flow
2
 
2
 
3
-import { CONNECTION_WILL_CONNECT } from '../connection';
3
+import { CONNECTION_WILL_CONNECT, SET_LOCATION_URL } from '../connection';
4
 import { JitsiConferenceErrors } from '../lib-jitsi-meet';
4
 import { JitsiConferenceErrors } from '../lib-jitsi-meet';
5
 import { assign, ReducerRegistry, set } from '../redux';
5
 import { assign, ReducerRegistry, set } from '../redux';
6
 import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
6
 import { LOCKED_LOCALLY, LOCKED_REMOTELY } from '../../room-lock';
66
     case SET_FOLLOW_ME:
66
     case SET_FOLLOW_ME:
67
         return set(state, 'followMeEnabled', action.enabled);
67
         return set(state, 'followMeEnabled', action.enabled);
68
 
68
 
69
+    case SET_LOCATION_URL:
70
+        return set(state, 'room', undefined);
71
+
69
     case SET_PASSWORD:
72
     case SET_PASSWORD:
70
         return _setPassword(state, action);
73
         return _setPassword(state, action);
71
 
74
 

+ 2
- 14
react/features/base/connection/actions.web.js Прегледај датотеку

2
 
2
 
3
 import type { Dispatch } from 'redux';
3
 import type { Dispatch } from 'redux';
4
 
4
 
5
-import { libInitError, WEBRTC_NOT_SUPPORTED } from '../lib-jitsi-meet';
6
-
7
 declare var APP: Object;
5
 declare var APP: Object;
8
 declare var config: Object;
6
 declare var config: Object;
9
 
7
 
26
 
24
 
27
         // XXX Lib-jitsi-meet does not accept uppercase letters.
25
         // XXX Lib-jitsi-meet does not accept uppercase letters.
28
         const room = state['features/base/conference'].room.toLowerCase();
26
         const room = state['features/base/conference'].room.toLowerCase();
29
-        const { initPromise } = state['features/base/lib-jitsi-meet'];
30
 
27
 
31
         // XXX For web based version we use conference initialization logic
28
         // XXX For web based version we use conference initialization logic
32
         // from the old app (at the moment of writing).
29
         // from the old app (at the moment of writing).
33
-        return initPromise.then(() => APP.conference.init({
30
+        return APP.conference.init({
34
             roomName: room
31
             roomName: room
35
-        })).catch(error => {
32
+        }).catch(error => {
36
             APP.API.notifyConferenceLeft(APP.conference.roomName);
33
             APP.API.notifyConferenceLeft(APP.conference.roomName);
37
             logger.error(error);
34
             logger.error(error);
38
-
39
-            // TODO The following are in fact Errors raised by
40
-            // JitsiMeetJS.init() which should be taken care of in
41
-            // features/base/lib-jitsi-meet but we are not there yet on the
42
-            // Web at the time of this writing.
43
-            switch (error.name) {
44
-            case WEBRTC_NOT_SUPPORTED:
45
-                dispatch(libInitError(error));
46
-            }
47
         });
35
         });
48
     };
36
     };
49
 }
37
 }

+ 0
- 20
react/features/base/lib-jitsi-meet/actionTypes.js Прегледај датотеку

27
  */
27
  */
28
 export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
28
 export const LIB_INIT_ERROR = Symbol('LIB_INIT_ERROR');
29
 
29
 
30
-/**
31
- * Action to dispatch the promise returned by JitsiMeetJS.init.
32
- *
33
- * {
34
- *     type: LIB_INIT_PROMISE_CREATED,
35
- *     initPromise: Promise
36
- * }
37
- */
38
-export const LIB_INIT_PROMISE_CREATED = Symbol('LIB_INIT_PROMISE_CREATED');
39
-
40
 /**
30
 /**
41
  * The type of Redux action which signals that {@link JitsiMeetJS} will be
31
  * The type of Redux action which signals that {@link JitsiMeetJS} will be
42
  * disposed.
32
  * disposed.
56
  * }
46
  * }
57
  */
47
  */
58
 export const LIB_WILL_INIT = Symbol('LIB_WILL_INIT');
48
 export const LIB_WILL_INIT = Symbol('LIB_WILL_INIT');
59
-
60
-/**
61
- * The type of Redux action which indicates whether WebRTC is ready.
62
- *
63
- * {
64
- *     type: SET_WEBRTC_READY,
65
- *     webRTCReady: boolean | Promise
66
- * }
67
- */
68
-export const SET_WEBRTC_READY = Symbol('SET_WEBRTC_READY');

+ 11
- 47
react/features/base/lib-jitsi-meet/actions.js Прегледај датотеку

7
     LIB_DID_DISPOSE,
7
     LIB_DID_DISPOSE,
8
     LIB_DID_INIT,
8
     LIB_DID_INIT,
9
     LIB_INIT_ERROR,
9
     LIB_INIT_ERROR,
10
-    LIB_INIT_PROMISE_CREATED,
11
     LIB_WILL_DISPOSE,
10
     LIB_WILL_DISPOSE,
12
-    LIB_WILL_INIT,
13
-    SET_WEBRTC_READY
11
+    LIB_WILL_INIT
14
 } from './actionTypes';
12
 } from './actionTypes';
15
 import { isAnalyticsEnabled } from './functions';
13
 import { isAnalyticsEnabled } from './functions';
16
 
14
 
38
  * @returns {Function}
36
  * @returns {Function}
39
  */
37
  */
40
 export function initLib() {
38
 export function initLib() {
41
-    return (dispatch: Dispatch<*>, getState: Function): Promise<void> => {
39
+    return (dispatch: Dispatch<*>, getState: Function): void => {
42
         const config = getState()['features/base/config'];
40
         const config = getState()['features/base/config'];
43
 
41
 
44
         if (!config) {
42
         if (!config) {
47
 
45
 
48
         dispatch({ type: LIB_WILL_INIT });
46
         dispatch({ type: LIB_WILL_INIT });
49
 
47
 
50
-        const initPromise = JitsiMeetJS.init({
51
-            enableAnalyticsLogging: isAnalyticsEnabled(getState),
52
-            ...config
53
-        });
54
-
55
-        dispatch({
56
-            type: LIB_INIT_PROMISE_CREATED,
57
-            initPromise
58
-        });
59
-
60
-        return (
61
-            initPromise
62
-                .then(() => dispatch({ type: LIB_DID_INIT }))
63
-                .catch(error => {
64
-                    // TODO: See the comment in the connect action in
65
-                    // base/connection/actions.web.js.
66
-                    if (typeof APP === 'undefined') {
67
-                        dispatch(libInitError(error));
68
-                    }
69
-
70
-                    // TODO Handle LIB_INIT_ERROR error somewhere instead.
71
-                    console.error('lib-jitsi-meet failed to init:', error);
72
-                    throw error;
73
-                }));
48
+        try {
49
+            JitsiMeetJS.init({
50
+                enableAnalyticsLogging: isAnalyticsEnabled(getState),
51
+                ...config
52
+            });
53
+            dispatch({ type: LIB_DID_INIT });
54
+        } catch (error) {
55
+            dispatch(libInitError(error));
56
+        }
74
     };
57
     };
75
 }
58
 }
76
 
59
 
89
         error
72
         error
90
     };
73
     };
91
 }
74
 }
92
-
93
-/**
94
- * Sets the indicator which determines whether WebRTC is ready.
95
- *
96
- * @param {boolean} webRTCReady - The indicator which determines
97
- * whether WebRTC is ready.
98
- * @returns {Function}
99
- */
100
-export function setWebRTCReady(webRTCReady: boolean) {
101
-    return (dispatch: Function, getState: Function) => {
102
-        if (getState()['features/base/lib-jitsi-meet'].webRTCReady
103
-                !== webRTCReady) {
104
-            dispatch({
105
-                type: SET_WEBRTC_READY,
106
-                webRTCReady
107
-            });
108
-        }
109
-    };
110
-}

+ 0
- 5
react/features/base/lib-jitsi-meet/constants.js Прегледај датотеку

1
-/**
2
- * The name of the Error thrown by {@link JitsiMeetJS.init()} which indicates
3
- * that WebRTC is not supported by the execution environment.
4
- */
5
-export const WEBRTC_NOT_SUPPORTED = 'WEBRTC_NOT_SUPPORTED';

+ 0
- 1
react/features/base/lib-jitsi-meet/index.js Прегледај датотеку

24
 
24
 
25
 export * from './actions';
25
 export * from './actions';
26
 export * from './actionTypes';
26
 export * from './actionTypes';
27
-export * from './constants';
28
 export * from './functions';
27
 export * from './functions';
29
 
28
 
30
 import './middleware';
29
 import './middleware';

+ 2
- 41
react/features/base/lib-jitsi-meet/middleware.js Прегледај датотеку

6
 import { MiddlewareRegistry } from '../redux';
6
 import { MiddlewareRegistry } from '../redux';
7
 
7
 
8
 import JitsiMeetJS from './_';
8
 import JitsiMeetJS from './_';
9
-import { disposeLib, initLib, setWebRTCReady } from './actions';
10
-import { LIB_DID_INIT, LIB_INIT_ERROR, LIB_WILL_INIT } from './actionTypes';
11
-import { WEBRTC_NOT_SUPPORTED } from './constants';
9
+import { disposeLib, initLib } from './actions';
10
+import { LIB_WILL_INIT } from './actionTypes';
12
 
11
 
13
 declare var APP: Object;
12
 declare var APP: Object;
14
 
13
 
31
             _setErrorHandlers();
30
             _setErrorHandlers();
32
         }
31
         }
33
         break;
32
         break;
34
-    case LIB_DID_INIT:
35
-        // FIXME: The web version doesn't need this action during initialization
36
-        // because it is still using the old logic from conference.js. We still
37
-        // have to reactify the old logic from conference.js and then maybe
38
-        // we'll need this action for web too.
39
-        if (typeof APP === 'undefined') {
40
-            store.dispatch(setWebRTCReady(true));
41
-        }
42
-        break;
43
-
44
-    case LIB_INIT_ERROR:
45
-        return _libInitError(store, next, action);
46
 
33
 
47
     case PARTICIPANT_LEFT:
34
     case PARTICIPANT_LEFT:
48
         action.participant.local && store.dispatch(disposeLib());
35
         action.participant.local && store.dispatch(disposeLib());
55
     return next(action);
42
     return next(action);
56
 });
43
 });
57
 
44
 
58
-/**
59
- * Notifies the feature base/lib-jitsi-meet that the action LIB_INIT_ERROR is
60
- * being dispatched within a specific Redux store.
61
- *
62
- * @param {Store} store - The Redux store in which the specified action is being
63
- * dispatched.
64
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
65
- * specified action to the specified store.
66
- * @param {Action} action - The Redux action LIB_INIT_ERROR which is being
67
- * dispatched in the specified store.
68
- * @private
69
- * @returns {Object} The new state that is the result of the reduction of the
70
- * specified action.
71
- */
72
-function _libInitError(store, next, action) {
73
-    const nextState = next(action);
74
-
75
-    const { error } = action;
76
-
77
-    if (error && error.name === WEBRTC_NOT_SUPPORTED) {
78
-        store.dispatch(setWebRTCReady(false));
79
-    }
80
-
81
-    return nextState;
82
-}
83
-
84
 /**
45
 /**
85
  * Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
46
  * Notifies the feature base/lib-jitsi-meet that the action SET_CONFIG is being
86
  * dispatched within a specific Redux store.
47
  * dispatched within a specific Redux store.

+ 2
- 17
react/features/base/lib-jitsi-meet/reducer.js Прегледај датотеку

5
 import {
5
 import {
6
     LIB_DID_DISPOSE,
6
     LIB_DID_DISPOSE,
7
     LIB_DID_INIT,
7
     LIB_DID_INIT,
8
-    LIB_INIT_ERROR,
9
-    LIB_INIT_PROMISE_CREATED,
10
-    SET_WEBRTC_READY
8
+    LIB_INIT_ERROR
11
 } from './actionTypes';
9
 } from './actionTypes';
12
 
10
 
13
 /**
11
 /**
35
             return {
33
             return {
36
                 ...state,
34
                 ...state,
37
                 initError: action.error,
35
                 initError: action.error,
38
-                initialized: false,
39
-                initPromise: undefined
40
-            };
41
-
42
-        case LIB_INIT_PROMISE_CREATED:
43
-            return {
44
-                ...state,
45
-                initPromise: action.initPromise
46
-            };
47
-
48
-        case SET_WEBRTC_READY:
49
-            return {
50
-                ...state,
51
-                webRTCReady: action.webRTCReady
36
+                initialized: false
52
             };
37
             };
53
 
38
 
54
         default:
39
         default:

+ 0
- 119
react/features/base/react/RouteRegistry.js Прегледај датотеку

1
-/* @flow */
2
-
3
-import { Component } from 'react';
4
-
5
-/**
6
- * Object describing application route.
7
- *
8
- * @typedef {Object} Route
9
- * @property {Component} component - React Component constructor.
10
- * @property {string} path - URL route, required for web routing.
11
- */
12
-type Route = {
13
-    component: Class<Component<*>>, // eslint-disable-line no-undef
14
-    path: string
15
-};
16
-
17
-/**
18
- * A registry for Navigator routes, allowing features to register themselves
19
- * without needing to create additional inter-feature dependencies.
20
- */
21
-class RouteRegistry {
22
-    _elements: Array<Route>;
23
-
24
-    /**
25
-     * Initializes a new RouteRegistry instance.
26
-     */
27
-    constructor() {
28
-        /**
29
-         * The set of registered routes.
30
-         *
31
-         * @private
32
-         * @type {Route[]}
33
-         */
34
-        this._elements = [];
35
-    }
36
-
37
-    /**
38
-     * Determines whether two specific Routes are equal i.e. they describe one
39
-     * and the same abstract route.
40
-     *
41
-     * @param {Object} a - The Route to compare to b.
42
-     * @param {Object} b - The Route to compare to a.
43
-     * @returns {boolean} True if the specified a and b describe one and the
44
-     * same abstract route; otherwise, false.
45
-     */
46
-    areRoutesEqual(a: Route, b: Route) {
47
-        if (a === b) { // reflexive
48
-            return true;
49
-        }
50
-        if (!a) {
51
-            return !b;
52
-        }
53
-        if (!b) {
54
-            return !a;
55
-        }
56
-
57
-        const aKeys = Object.keys(a);
58
-        const bKeys = Object.keys(b);
59
-
60
-        return (
61
-            aKeys.length === bKeys.length /* symmetric */
62
-                && aKeys.every(key => a[key] === b[key]));
63
-    }
64
-
65
-    /**
66
-     * Returns all registered routes.
67
-     *
68
-     * @returns {Route[]}
69
-     */
70
-    getRoutes() {
71
-        // We use the destructuring operator to 'clone' the route object to
72
-        // prevent modifications from outside (e.g. React Native's Navigator
73
-        // extends it with additional properties).
74
-        return this._elements.map(r => {
75
-            return { ...r };
76
-        });
77
-    }
78
-
79
-    /* eslint-disable no-undef */
80
-
81
-    /**
82
-     * Returns registered route by name if any.
83
-     *
84
-     * @param {Component} component - The React Component (class) of the route
85
-     * to retrieve.
86
-     * @returns {Route|null}
87
-     */
88
-    getRouteByComponent(component: Class<Component<*>>) {
89
-
90
-        /* eslint-enable no-undef */
91
-
92
-        const route = this._elements.find(r => r.component === component);
93
-
94
-        // We use destructuring operator to 'clone' route object to prevent
95
-        // modifications from outside (e.g. React Native's Navigator extends
96
-        // it with some additional properties).
97
-        return route ? { ...route } : null;
98
-    }
99
-
100
-    /**
101
-     * Adds a route to this registry.
102
-     *
103
-     * @param {Route} route - Route definition object.
104
-     * @returns {void}
105
-     */
106
-    register(route: Route) {
107
-        if (this._elements.includes(route)) {
108
-            throw new Error(
109
-                `Route ${String(route.component)} is registered already!`);
110
-        }
111
-
112
-        this._elements.push(route);
113
-    }
114
-}
115
-
116
-/**
117
- * The public singleton instance of the RouteRegistry class.
118
- */
119
-export default new RouteRegistry();

+ 0
- 1
react/features/base/react/index.js Прегледај датотеку

1
 export * from './components';
1
 export * from './components';
2
 export { default as Platform } from './Platform';
2
 export { default as Platform } from './Platform';
3
-export { default as RouteRegistry } from './RouteRegistry';

+ 57
- 4
react/features/conference/components/Conference.native.js Прегледај датотеку

37
      */
37
      */
38
     _connecting: boolean,
38
     _connecting: boolean,
39
 
39
 
40
+    /**
41
+     * Current conference's full URL.
42
+     *
43
+     * @private
44
+     */
45
+    _locationURL: URL,
46
+
40
     /**
47
     /**
41
      * The handler which dispatches the (redux) action connect.
48
      * The handler which dispatches the (redux) action connect.
42
      *
49
      *
77
      */
84
      */
78
     _reducedUI: boolean,
85
     _reducedUI: boolean,
79
 
86
 
87
+    /**
88
+     * The current conference room name.
89
+     *
90
+     * @private
91
+     */
92
+    _room: string,
93
+
80
     /**
94
     /**
81
      * The handler which dispatches the (redux) action setToolboxVisible to
95
      * The handler which dispatches the (redux) action setToolboxVisible to
82
      * show/hide the Toolbox.
96
      * show/hide the Toolbox.
163
      * participant count.
177
      * participant count.
164
      *
178
      *
165
      * @inheritdoc
179
      * @inheritdoc
166
-     * @param {Object} nextProps - The read-only React {@code Component} props
180
+     * @param {Props} nextProps - The read-only React {@code Component} props
167
      * that this instance will receive.
181
      * that this instance will receive.
168
      * @returns {void}
182
      * @returns {void}
169
      */
183
      */
170
-    componentWillReceiveProps({ _participantCount: newParticipantCount }) {
184
+    componentWillReceiveProps(nextProps: Props) {
171
         const {
185
         const {
186
+            _locationURL: oldLocationURL,
187
+            _onConnect,
188
+            _onDisconnect,
172
             _participantCount: oldParticipantCount,
189
             _participantCount: oldParticipantCount,
190
+            _room: oldRoom,
173
             _setToolboxVisible
191
             _setToolboxVisible
174
         } = this.props;
192
         } = this.props;
193
+        const {
194
+            _locationURL: newLocationURL,
195
+            _participantCount: newParticipantCount,
196
+            _room: newRoom
197
+        } = nextProps;
198
+
199
+        // If the location URL changes we need to reconnect.
200
+        oldLocationURL !== newLocationURL && _onDisconnect();
201
+
202
+        // Start the connection process when there is a (valid) room.
203
+        oldRoom !== newRoom && newRoom && _onConnect();
175
 
204
 
176
         if (oldParticipantCount === 1) {
205
         if (oldParticipantCount === 1) {
177
             newParticipantCount > 1 && _setToolboxVisible(false);
206
             newParticipantCount > 1 && _setToolboxVisible(false);
389
  * @private
418
  * @private
390
  * @returns {{
419
  * @returns {{
391
  *     _connecting: boolean,
420
  *     _connecting: boolean,
421
+ *     _locationURL: URL,
392
  *     _participantCount: number,
422
  *     _participantCount: number,
393
  *     _reducedUI: boolean,
423
  *     _reducedUI: boolean,
424
+ *     _room: string,
394
  *     _toolboxVisible: boolean,
425
  *     _toolboxVisible: boolean,
395
  *     _toolboxAlwaysVisible: boolean
426
  *     _toolboxAlwaysVisible: boolean
396
  * }}
427
  * }}
397
  */
428
  */
398
 function _mapStateToProps(state) {
429
 function _mapStateToProps(state) {
399
-    const { connecting, connection } = state['features/base/connection'];
400
-    const { conference, joining, leaving } = state['features/base/conference'];
430
+    const { connecting, connection, locationURL }
431
+        = state['features/base/connection'];
432
+    const {
433
+        conference,
434
+        joining,
435
+        leaving,
436
+        room
437
+    } = state['features/base/conference'];
401
     const { reducedUI } = state['features/base/responsive-ui'];
438
     const { reducedUI } = state['features/base/responsive-ui'];
402
     const { alwaysVisible, visible } = state['features/toolbox'];
439
     const { alwaysVisible, visible } = state['features/toolbox'];
403
 
440
 
425
          */
462
          */
426
         _connecting: Boolean(connecting_),
463
         _connecting: Boolean(connecting_),
427
 
464
 
465
+        /**
466
+         * Current conference's full URL.
467
+         *
468
+         * @private
469
+         * @type {URL}
470
+         */
471
+        _locationURL: locationURL,
472
+
428
         /**
473
         /**
429
          * The number of participants in the conference.
474
          * The number of participants in the conference.
430
          *
475
          *
442
          */
487
          */
443
         _reducedUI: reducedUI,
488
         _reducedUI: reducedUI,
444
 
489
 
490
+        /**
491
+         * The current conference room name.
492
+         *
493
+         * @private
494
+         * @type {string}
495
+         */
496
+        _room: room,
497
+
445
         /**
498
         /**
446
          * The indicator which determines whether the Toolbox is visible.
499
          * The indicator which determines whether the Toolbox is visible.
447
          *
500
          *

+ 85
- 20
react/features/conference/components/Conference.web.js Прегледај датотеку

4
 import React, { Component } from 'react';
4
 import React, { Component } from 'react';
5
 import { connect as reactReduxConnect } from 'react-redux';
5
 import { connect as reactReduxConnect } from 'react-redux';
6
 
6
 
7
+import { obtainConfig } from '../../base/config';
7
 import { connect, disconnect } from '../../base/connection';
8
 import { connect, disconnect } from '../../base/connection';
8
 import { DialogContainer } from '../../base/dialog';
9
 import { DialogContainer } from '../../base/dialog';
9
 import { translate } from '../../base/i18n';
10
 import { translate } from '../../base/i18n';
22
 import { maybeShowSuboptimalExperienceNotification } from '../functions';
23
 import { maybeShowSuboptimalExperienceNotification } from '../functions';
23
 
24
 
24
 declare var APP: Object;
25
 declare var APP: Object;
26
+declare var config: Object;
25
 declare var interfaceConfig: Object;
27
 declare var interfaceConfig: Object;
26
 
28
 
29
+const logger = require('jitsi-meet-logger').getLogger(__filename);
30
+
31
+/**
32
+ * Promise wrapper on obtain config method. When HttpConfigFetch will be moved
33
+ * to React app it's better to use load config instead.
34
+ *
35
+ * @param {string} location - URL of the domain from which the config is to be
36
+ * obtained.
37
+ * @param {string} room - Room name.
38
+ * @private
39
+ * @returns {Promise}
40
+ */
41
+function _obtainConfig(location: string, room: string) {
42
+    return new Promise((resolve, reject) =>
43
+        obtainConfig(location, room, (success, error) => {
44
+            success ? resolve() : reject(error);
45
+        })
46
+    );
47
+}
48
+
27
 /**
49
 /**
28
  * DOM events for when full screen mode has changed. Different browsers need
50
  * DOM events for when full screen mode has changed. Different browsers need
29
  * different vendor prefixes.
51
  * different vendor prefixes.
47
      */
69
      */
48
     _iAmRecorder: boolean,
70
     _iAmRecorder: boolean,
49
 
71
 
72
+    /**
73
+     * Conference room name.
74
+     */
75
+    _room: string,
76
+
50
     dispatch: Function,
77
     dispatch: Function,
51
     t: Function
78
     t: Function
52
 }
79
 }
84
     }
111
     }
85
 
112
 
86
     /**
113
     /**
87
-     * Until we don't rewrite UI using react components
88
-     * we use UI.start from old app. Also method translates
89
-     * component right after it has been mounted.
114
+     * Start the connection and get the UI ready for the conference.
90
      *
115
      *
91
      * @inheritdoc
116
      * @inheritdoc
92
      */
117
      */
93
     componentDidMount() {
118
     componentDidMount() {
94
-        APP.UI.start();
95
-
96
-        APP.UI.registerListeners();
97
-        APP.UI.bindEvents();
98
-
99
-        FULL_SCREEN_EVENTS.forEach(name =>
100
-            document.addEventListener(name, this._onFullScreenChange));
101
-
102
-        const { dispatch, t } = this.props;
103
-
104
-        dispatch(connect());
105
-
106
-        maybeShowSuboptimalExperienceNotification(dispatch, t);
107
-
108
-        interfaceConfig.filmStripOnly
109
-            && dispatch(setToolboxAlwaysVisible(true));
119
+        const { configLocation } = config;
120
+
121
+        if (configLocation) {
122
+            _obtainConfig(configLocation, this.props._room)
123
+                .then(() => {
124
+                    const now = window.performance.now();
125
+
126
+                    APP.connectionTimes['configuration.fetched'] = now;
127
+                    logger.log('(TIME) configuration fetched:\t', now);
128
+
129
+                    this._start();
130
+                })
131
+                .catch(err => {
132
+                    logger.log(err);
133
+
134
+                    // Show obtain config error.
135
+                    APP.UI.messageHandler.showError({
136
+                        titleKey: 'connection.CONNFAIL',
137
+                        descriptionKey: 'dialog.connectError'
138
+                    });
139
+                });
140
+        } else {
141
+            this._start();
142
+        }
110
     }
143
     }
111
 
144
 
112
     /**
145
     /**
185
     _onShowToolbar() {
218
     _onShowToolbar() {
186
         this.props.dispatch(showToolbox());
219
         this.props.dispatch(showToolbox());
187
     }
220
     }
221
+
222
+    /**
223
+     * Until we don't rewrite UI using react components
224
+     * we use UI.start from old app. Also method translates
225
+     * component right after it has been mounted.
226
+     *
227
+     * @inheritdoc
228
+     */
229
+    _start() {
230
+        APP.UI.start();
231
+
232
+        APP.UI.registerListeners();
233
+        APP.UI.bindEvents();
234
+
235
+        FULL_SCREEN_EVENTS.forEach(name =>
236
+            document.addEventListener(name, this._onFullScreenChange));
237
+
238
+        const { dispatch, t } = this.props;
239
+
240
+        dispatch(connect());
241
+
242
+        maybeShowSuboptimalExperienceNotification(dispatch, t);
243
+
244
+        interfaceConfig.filmStripOnly
245
+            && dispatch(setToolboxAlwaysVisible(true));
246
+    }
188
 }
247
 }
189
 
248
 
190
 /**
249
 /**
198
  * }}
257
  * }}
199
  */
258
  */
200
 function _mapStateToProps(state) {
259
 function _mapStateToProps(state) {
260
+    const { room } = state['features/base/conference'];
201
     const { iAmRecorder } = state['features/base/config'];
261
     const { iAmRecorder } = state['features/base/config'];
202
 
262
 
203
     return {
263
     return {
206
          *
266
          *
207
          * @private
267
          * @private
208
          */
268
          */
209
-        _iAmRecorder: iAmRecorder
269
+        _iAmRecorder: iAmRecorder,
270
+
271
+        /**
272
+         * Conference room name.
273
+         */
274
+        _room: room
210
     };
275
     };
211
 }
276
 }
212
 
277
 

+ 0
- 1
react/features/conference/index.js Прегледај датотеку

1
 export * from './components';
1
 export * from './components';
2
 
2
 
3
 import './middleware';
3
 import './middleware';
4
-import './route';

+ 0
- 105
react/features/conference/route.js Прегледај датотеку

1
-// @flow
2
-
3
-import ConferenceUrl from '../../../modules/URL/ConferenceUrl';
4
-
5
-import { obtainConfig } from '../base/config';
6
-import { RouteRegistry } from '../base/react';
7
-
8
-import { Conference } from './components';
9
-
10
-declare var APP: Object;
11
-declare var config: Object;
12
-
13
-const logger = require('jitsi-meet-logger').getLogger(__filename);
14
-
15
-/**
16
- * Register route for Conference (page).
17
- */
18
-RouteRegistry.register({
19
-    component: Conference,
20
-    onEnter: () => {
21
-        // XXX If config or jwt are set by hash or query parameters
22
-        // Getting raw URL before stripping it.
23
-        _obtainConfigAndInit();
24
-    },
25
-    path: '/:room'
26
-});
27
-
28
-/**
29
- * Initialization of the app.
30
- *
31
- * @private
32
- * @returns {void}
33
- */
34
-function _initConference() {
35
-    APP.ConferenceUrl = new ConferenceUrl(window.location);
36
-}
37
-
38
-/**
39
- * Promise wrapper on obtain config method. When HttpConfigFetch will be moved
40
- * to React app it's better to use load config instead.
41
- *
42
- * @param {string} location - URL of the domain from which the config is to be
43
- * obtained.
44
- * @param {string} room - Room name.
45
- * @private
46
- * @returns {Promise}
47
- */
48
-function _obtainConfig(location: string, room: string) {
49
-    return new Promise((resolve, reject) =>
50
-        obtainConfig(location, room, (success, error) => {
51
-            success ? resolve() : reject(error);
52
-        })
53
-    );
54
-}
55
-
56
-/**
57
- * If we have an HTTP endpoint for getting config.json configured we're going to
58
- * read it and override properties from config.js and interfaceConfig.js. If
59
- * there is no endpoint we'll just continue with initialization. Keep in mind
60
- * that if the endpoint has been configured and we fail to obtain the config for
61
- * any reason then the conference won't start and error message will be
62
- * displayed to the user.
63
- *
64
- * @private
65
- * @returns {void}
66
- */
67
-function _obtainConfigAndInit() {
68
-    // Skip initialization if conference is initialized already.
69
-    if (typeof APP !== 'undefined' && !APP.ConferenceUrl) {
70
-        const location = config.configLocation;
71
-        const room = APP.conference.roomName;
72
-
73
-        if (location) {
74
-            _obtainConfig(location, room)
75
-                .then(() => {
76
-                    _obtainConfigHandler();
77
-                    _initConference();
78
-                })
79
-                .catch(err => {
80
-                    logger.log(err);
81
-
82
-                    // Show obtain config error.
83
-                    APP.UI.messageHandler.showError({
84
-                        titleKey: 'connection.CONNFAIL',
85
-                        descriptionKey: 'dialog.connectError'
86
-                    });
87
-                });
88
-        } else {
89
-            _initConference();
90
-        }
91
-    }
92
-}
93
-
94
-/**
95
- * Obtain config handler.
96
- *
97
- * @private
98
- * @returns {Promise}
99
- */
100
-function _obtainConfigHandler() {
101
-    const now = window.performance.now();
102
-
103
-    APP.connectionTimes['configuration.fetched'] = now;
104
-    logger.log('(TIME) configuration fetched:\t', now);
105
-}

+ 0
- 0
react/features/deep-linking/components/DeepLinkingDesktopPage.native.js Прегледај датотеку


react/features/deep-linking/components/DeepLinkingDesktopPage.js → react/features/deep-linking/components/DeepLinkingDesktopPage.web.js Прегледај датотеку


+ 0
- 0
react/features/deep-linking/components/DeepLinkingMobilePage.native.js Прегледај датотеку


react/features/deep-linking/components/DeepLinkingMobilePage.js → react/features/deep-linking/components/DeepLinkingMobilePage.web.js Прегледај датотеку


+ 0
- 0
react/features/deep-linking/components/NoMobileApp.native.js Прегледај датотеку


react/features/deep-linking/components/NoMobileApp.js → react/features/deep-linking/components/NoMobileApp.web.js Прегледај датотеку


+ 1
- 4
react/features/device-selection/actions.js Прегледај датотеку

94
 
94
 
95
         switch (request.name) {
95
         switch (request.name) {
96
         case 'isDeviceListAvailable':
96
         case 'isDeviceListAvailable':
97
-            JitsiMeetJS.mediaDevices.isDeviceListAvailable()
98
-                .then(isDeviceListAvailable =>
99
-                    responseCallback(isDeviceListAvailable))
100
-                .catch(e => responseCallback(null, e));
97
+            responseCallback(JitsiMeetJS.mediaDevices.isDeviceListAvailable());
101
             break;
98
             break;
102
         case 'isDeviceChangeAvailable':
99
         case 'isDeviceChangeAvailable':
103
             responseCallback(
100
             responseCallback(

+ 2
- 3
react/features/settings/popup.js Прегледај датотеку

8
 let deviceSelectionPopup;
8
 let deviceSelectionPopup;
9
 
9
 
10
 window.init = i18next => {
10
 window.init = i18next => {
11
-    JitsiMeetJS.init({}).then(() => {
12
-        deviceSelectionPopup = new DeviceSelectionPopup(i18next);
13
-    });
11
+    JitsiMeetJS.init();
12
+    deviceSelectionPopup = new DeviceSelectionPopup(i18next);
14
 };
13
 };
15
 
14
 
16
 window.addEventListener('beforeunload', () => deviceSelectionPopup.close());
15
 window.addEventListener('beforeunload', () => deviceSelectionPopup.close());

+ 0
- 2
react/features/unsupported-browser/index.js Прегледај датотеку

1
 export * from './components';
1
 export * from './components';
2
-
3
-import './middleware';

+ 0
- 54
react/features/unsupported-browser/middleware.js Прегледај датотеку

1
-// @flow
2
-
3
-import { appNavigate } from '../app';
4
-import { SET_WEBRTC_READY } from '../base/lib-jitsi-meet';
5
-import { MiddlewareRegistry } from '../base/redux';
6
-
7
-/**
8
- * Middleware that dispatches appNavigate when WebRTC readiness changes.
9
- *
10
- * @param {Store} store - The Redux store.
11
- * @returns {Function}
12
- * @private
13
- */
14
-MiddlewareRegistry.register(store => next => action => {
15
-    switch (action.type) {
16
-    case SET_WEBRTC_READY:
17
-        return _setWebRTCReady(store, next, action);
18
-    }
19
-
20
-    return next(action);
21
-});
22
-
23
-/**
24
- * Notifies the feature unsupported-browser that the action SET_WEBRTC_READY is
25
- * being dispatched within a specific Redux store.
26
- *
27
- * @param {Store} store - The Redux store in which the specified action is being
28
- * dispatched.
29
- * @param {Dispatch} next - The Redux dispatch function to dispatch the
30
- * specified action to the specified store.
31
- * @param {Action} action - The Redux action SET_WEBRTC_READY which is being
32
- * dispatched in the specified store.
33
- * @private
34
- * @returns {Object} The new state that is the result of the reduction of the
35
- * specified action.
36
- */
37
-function _setWebRTCReady({ dispatch, getState }, next, action) {
38
-    const result = next(action);
39
-
40
-    // FIXME The feature unsupported-browser needs to notify the app that it may
41
-    // need to render a different Component at its current location because the
42
-    // execution enviroment has changed. The current location is not necessarily
43
-    // available through window.location (e.g. on mobile) but the following
44
-    // works at the time of this writing.
45
-    const windowLocation = getState()['features/app'].app.getWindowLocation();
46
-
47
-    if (windowLocation) {
48
-        const { href } = windowLocation;
49
-
50
-        href && dispatch(appNavigate(href));
51
-    }
52
-
53
-    return result;
54
-}

+ 12
- 40
react/features/welcome/components/BlankPage.native.js Прегледај датотеку

1
-/* @flow */
1
+// @flow
2
 
2
 
3
-import PropTypes from 'prop-types';
4
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
5
 import { connect } from 'react-redux';
4
 import { connect } from 'react-redux';
5
+import type { Dispatch } from 'redux';
6
 
6
 
7
 import { destroyLocalTracks } from '../../base/tracks';
7
 import { destroyLocalTracks } from '../../base/tracks';
8
 import { NetworkActivityIndicator } from '../../mobile/network-activity';
8
 import { NetworkActivityIndicator } from '../../mobile/network-activity';
9
 
9
 
10
-import { isWelcomePageAppEnabled } from '../functions';
11
 import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
10
 import LocalVideoTrackUnderlay from './LocalVideoTrackUnderlay';
12
 
11
 
12
+/**
13
+ * {@code BlankPage} React {@code Component}'s prop types.
14
+ */
15
+type Props = {
16
+    dispatch: Dispatch<*>
17
+}
18
+
13
 /**
19
 /**
14
  * The React {@code Component} displayed by {@code AbstractApp} when it has no
20
  * The React {@code Component} displayed by {@code AbstractApp} when it has no
15
  * {@code Route} to render. Renders a progress indicator when there are ongoing
21
  * {@code Route} to render. Renders a progress indicator when there are ongoing
16
  * network requests.
22
  * network requests.
17
  */
23
  */
18
-class BlankPage extends Component<*> {
19
-    /**
20
-     * {@code BlankPage} React {@code Component}'s prop types.
21
-     *
22
-     * @static
23
-     */
24
-    static propTypes = {
25
-        /**
26
-         * The indicator which determines whether {@code WelcomePage} is (to
27
-         * be) rendered.
28
-         *
29
-         * @private
30
-         */
31
-        _welcomePageEnabled: PropTypes.bool,
32
-
33
-        dispatch: PropTypes.func
34
-    };
35
-
24
+class BlankPage extends Component<Props> {
36
     /**
25
     /**
37
      * Destroys the local tracks (if any) since no media is desired when this
26
      * Destroys the local tracks (if any) since no media is desired when this
38
      * component is rendered.
27
      * component is rendered.
41
      * @returns {void}
30
      * @returns {void}
42
      */
31
      */
43
     componentWillMount() {
32
     componentWillMount() {
44
-        this.props._welcomePageEnabled
45
-            || this.props.dispatch(destroyLocalTracks());
33
+        this.props.dispatch(destroyLocalTracks());
46
     }
34
     }
47
 
35
 
48
     /**
36
     /**
60
     }
48
     }
61
 }
49
 }
62
 
50
 
63
-/**
64
- * Maps (parts of) the redux state to the React {@code Component} props of
65
- * {@code BlankPage}.
66
- *
67
- * @param {Object} state - The redux state.
68
- * @private
69
- * @returns {{
70
- *     _welcomePageEnabled: boolean
71
- * }}
72
- */
73
-function _mapStateToProps(state) {
74
-    return {
75
-        _welcomePageEnabled: isWelcomePageAppEnabled(state)
76
-    };
77
-}
78
-
79
-export default connect(_mapStateToProps)(BlankPage);
51
+export default connect()(BlankPage);

+ 2
- 6
react/features/welcome/functions.js Прегледај датотеку

19
  * {@code true}; otherwise, {@code false}.
19
  * {@code true}; otherwise, {@code false}.
20
  */
20
  */
21
 export function isWelcomePageAppEnabled(stateful: Function | Object) {
21
 export function isWelcomePageAppEnabled(stateful: Function | Object) {
22
-    let b;
23
-
24
     if (navigator.product === 'ReactNative') {
22
     if (navigator.product === 'ReactNative') {
25
         // We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
23
         // We introduced the welcomePageEnabled prop on App in Jitsi Meet SDK
26
         // for Android and iOS. There isn't a strong reason not to introduce it
24
         // for Android and iOS. There isn't a strong reason not to introduce it
29
         // - Enabling/disabling the Welcome page on Web historically
27
         // - Enabling/disabling the Welcome page on Web historically
30
         // automatically redirects to a random room and that does not make sense
28
         // automatically redirects to a random room and that does not make sense
31
         // on mobile (right now).
29
         // on mobile (right now).
32
-        b = Boolean(getAppProp(stateful, 'welcomePageEnabled'));
33
-    } else {
34
-        b = true;
30
+        return Boolean(getAppProp(stateful, 'welcomePageEnabled'));
35
     }
31
     }
36
 
32
 
37
-    return b;
33
+    return true;
38
 }
34
 }
39
 
35
 
40
 /**
36
 /**

+ 0
- 1
react/features/welcome/index.js Прегледај датотеку

2
 export * from './functions';
2
 export * from './functions';
3
 
3
 
4
 import './reducer';
4
 import './reducer';
5
-import './route';

+ 0
- 35
react/features/welcome/route.js Прегледај датотеку

1
-/* @flow */
2
-
3
-import { RouteRegistry } from '../base/react';
4
-
5
-import { WelcomePage } from './components';
6
-import {
7
-    generateRoomWithoutSeparator,
8
-    isWelcomePageAppEnabled,
9
-    isWelcomePageUserEnabled
10
-} from './functions';
11
-
12
-/**
13
- * Register route for {@code WelcomePage}.
14
- */
15
-RouteRegistry.register({
16
-    component: WelcomePage,
17
-    onEnter,
18
-    path: '/'
19
-});
20
-
21
-/**
22
- * Skips the {@code WelcomePage} if it is disabled (by the app or the user).
23
- *
24
- * @param {Object} store - The redux store.
25
- * @param {Function} replace - The function to redirect to another path.
26
- * @returns {void}
27
- */
28
-function onEnter({ getState }, replace) {
29
-    if (isWelcomePageAppEnabled(getState)) {
30
-        isWelcomePageUserEnabled(getState)
31
-            || replace(`/${generateRoomWithoutSeparator()}`);
32
-    } else {
33
-        replace(undefined);
34
-    }
35
-}

Loading…
Откажи
Сачувај