diff --git a/bigbluebutton-html5/client/main.jsx b/bigbluebutton-html5/client/main.jsx
index 893beb2ef4..8a0d1df182 100755
--- a/bigbluebutton-html5/client/main.jsx
+++ b/bigbluebutton-html5/client/main.jsx
@@ -7,6 +7,7 @@ import Base from '/imports/startup/client/base';
import JoinHandler from '/imports/ui/components/join-handler/component';
import AuthenticatedHandler from '/imports/ui/components/authenticated-handler/component';
import Subscriptions from '/imports/ui/components/subscriptions/component';
+import JoinLoading from '/imports/ui/components/join-loading/component';
Meteor.startup(() => {
// Logs all uncaught exceptions to the client logger
@@ -33,13 +34,15 @@ Meteor.startup(() => {
// TODO make this a Promise
render(
-
-
-
-
-
-
- ,
+
+
+
+
+
+
+
+
+ ,
document.getElementById('app'),
);
});
diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx
index eae2a5cbc0..7b4dbe945e 100755
--- a/bigbluebutton-html5/imports/startup/client/base.jsx
+++ b/bigbluebutton-html5/imports/startup/client/base.jsx
@@ -3,9 +3,7 @@ import { withTracker } from 'meteor/react-meteor-data';
import PropTypes from 'prop-types';
import Auth from '/imports/ui/services/auth';
import AppContainer from '/imports/ui/components/app/container';
-import ErrorScreen from '/imports/ui/components/error-screen/component';
import MeetingEnded from '/imports/ui/components/meeting-ended/component';
-import LoadingScreen from '/imports/ui/components/loading-screen/component';
import Settings from '/imports/ui/services/settings';
import AudioManager from '/imports/ui/services/audio-manager';
import logger from '/imports/startup/client/logger';
@@ -18,6 +16,7 @@ import Breakouts from '/imports/api/breakouts';
import AudioService from '/imports/ui/components/audio/service';
import { FormattedMessage } from 'react-intl';
import { notify } from '/imports/ui/services/notification';
+import { withJoinLoadingConsumer } from '/imports/ui/components/join-loading/context/context';
const HTML = document.getElementsByTagName('html')[0];
@@ -87,12 +86,29 @@ class Base extends Component {
ejected,
isMeteorConnected,
subscriptionsReady,
+ codeError,
+ meetingHasEnded,
+ dispatch,
} = this.props;
const {
loading,
meetingExisted,
} = this.state;
+ if (codeError && !meetingHasEnded) {
+ // 680 is set for the codeError when the user requests a logout
+ if (codeError !== '680') {
+ logger.error({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${codeError}`);
+ }
+ Session.set('codeError', codeError);
+ return dispatch('hasError');
+ }
+
+ if (!(loading || !subscriptionsReady)
+ && !meetingHasEnded && meetingExist) {
+ dispatch('hideLoading');
+ }
+
if (!prevProps.subscriptionsReady && subscriptionsReady) {
logger.info({ logCode: 'startup_client_subscriptions_ready' }, 'Subscriptions are ready');
}
@@ -152,21 +168,14 @@ class Base extends Component {
renderByState() {
const { updateLoadingState } = this;
const stateControls = { updateLoadingState };
- const { loading } = this.state;
const codeError = Session.get('codeError');
const {
ejected,
- meetingExist,
meetingHasEnded,
meetingIsBreakout,
- subscriptionsReady,
User,
} = this.props;
- if ((loading || !subscriptionsReady) && !meetingHasEnded && meetingExist) {
- return ({loading});
- }
-
if (ejected && ejected.ejectedReason) {
const { ejectedReason } = ejected;
AudioManager.exitAudio();
@@ -179,14 +188,6 @@ class Base extends Component {
AudioManager.exitAudio();
return ();
}
-
- if (codeError && !meetingHasEnded) {
- // 680 is set for the codeError when the user requests a logout
- if (codeError !== '680') {
- logger.error({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${codeError}`);
- }
- return ();
- }
// this.props.annotationsHandler.stop();
return ();
}
@@ -196,15 +197,11 @@ class Base extends Component {
const { locale, meetingExist } = this.props;
const stateControls = { updateLoadingState };
const { meetingExisted } = this.state;
-
+ if ((!meetingExisted && !meetingExist && Auth.loggedIn)) return null;
return (
- (!meetingExisted && !meetingExist && Auth.loggedIn)
- ?
- : (
-
- {this.renderByState()}
-
- )
+
+ {this.renderByState()}
+
);
}
}
@@ -316,4 +313,4 @@ const BaseContainer = withTracker(() => {
};
})(Base);
-export default BaseContainer;
+export default withJoinLoadingConsumer(BaseContainer);
diff --git a/bigbluebutton-html5/imports/startup/client/intl.jsx b/bigbluebutton-html5/imports/startup/client/intl.jsx
index c93805c743..8f541853ec 100644
--- a/bigbluebutton-html5/imports/startup/client/intl.jsx
+++ b/bigbluebutton-html5/imports/startup/client/intl.jsx
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { IntlProvider, addLocaleData } from 'react-intl';
import Settings from '/imports/ui/services/settings';
-import LoadingScreen from '/imports/ui/components/loading-screen/component';
+import { withJoinLoadingConsumer } from '/imports/ui/components/join-loading/context/context';
// currently supported locales.
import ar from 'react-intl/locale-data/ar';
@@ -106,7 +106,7 @@ class IntlStartup extends Component {
fetchLocalizedMessages(locale) {
const url = `/html5client/locale?locale=${locale}`;
-
+ const { dispatch } = this.props;
this.setState({ fetching: true }, () => {
fetch(url)
.then((response) => {
@@ -120,6 +120,7 @@ class IntlStartup extends Component {
const dasherizedLocale = normalizedLocale.replace('_', '-');
this.setState({ messages, fetching: false, normalizedLocale: dasherizedLocale }, () => {
this.saveLocale(dasherizedLocale);
+ dispatch('hideLoading');
});
})
.catch(() => {
@@ -146,7 +147,7 @@ class IntlStartup extends Component {
const { fetching, normalizedLocale, messages } = this.state;
const { children } = this.props;
- return fetching ? : (
+ return !fetching && (
{children}
@@ -154,7 +155,7 @@ class IntlStartup extends Component {
}
}
-export default IntlStartup;
+export default withJoinLoadingConsumer(IntlStartup);
IntlStartup.propTypes = propTypes;
IntlStartup.defaultProps = defaultProps;
diff --git a/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx
index 209052abd1..f13b2bd894 100644
--- a/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/authenticated-handler/component.jsx
@@ -2,18 +2,13 @@ import React, { Component } from 'react';
import { Session } from 'meteor/session';
import logger from '/imports/startup/client/logger';
import Auth from '/imports/ui/services/auth';
-import LoadingScreen from '/imports/ui/components/loading-screen/component';
+import { withJoinLoadingConsumer } from '/imports/ui/components/join-loading/context/context';
const STATUS_CONNECTING = 'connecting';
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
class AuthenticatedHandler extends Component {
- static setError(codeError) {
- Session.set('hasError', true);
- if (codeError) Session.set('codeError', codeError);
- }
-
static shouldAuthenticate(status, lastStatus) {
return lastStatus != null && lastStatus === STATUS_CONNECTING && status.connected;
}
@@ -35,7 +30,32 @@ class AuthenticatedHandler extends Component {
});
}
- static async authenticatedRouteHandler(callback) {
+ constructor(props) {
+ super(props);
+ this.setError = this.setError.bind(this);
+ this.authenticatedRouteHandler = this.authenticatedRouteHandler.bind(this);
+ this.state = {
+ authenticated: false,
+ };
+ }
+
+ componentDidMount() {
+ if (Session.get('codeError')) {
+ return this.setError(Session.get('codeError'));
+ }
+ return this.authenticatedRouteHandler((value, error) => {
+ if (error) return this.setError(error);
+ return this.setState({ authenticated: true });
+ });
+ }
+
+ setError(codeError) {
+ const { dispatch } = this.props;
+ dispatch('hasError');
+ if (codeError) Session.set('codeError', codeError);
+ }
+
+ async authenticatedRouteHandler(callback) {
if (Auth.loggedIn) {
callback();
}
@@ -43,6 +63,7 @@ class AuthenticatedHandler extends Component {
AuthenticatedHandler.addReconnectObservable();
const setReason = (reason) => {
+ this.setError(reason.error);
logger.error({
logCode: 'authenticatedhandlercomponent_setreason',
extraInfo: { reason },
@@ -60,21 +81,6 @@ class AuthenticatedHandler extends Component {
}
}
- constructor(props) {
- super(props);
- this.state = {
- authenticated: false,
- };
- }
-
- componentDidMount() {
- if (Session.get('codeError')) return this.changeState(true);
- AuthenticatedHandler.authenticatedRouteHandler((value, error) => {
- if (error) AuthenticatedHandler.setError(error);
- this.setState({ authenticated: true });
- });
- }
-
render() {
const {
children,
@@ -89,11 +95,9 @@ class AuthenticatedHandler extends Component {
Session.set('isPollOpen', false);
Session.set('breakoutRoomIsOpen', false);
- return authenticated
- ? children
- : ();
+ return authenticated && children;
}
}
-export default AuthenticatedHandler;
+export default withJoinLoadingConsumer(AuthenticatedHandler);
diff --git a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx
index bfcf865a4e..609b7f6dee 100644
--- a/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/error-screen/component.jsx
@@ -57,16 +57,16 @@ class ErrorScreen extends React.PureComponent {
children,
} = this.props;
+ const codeError = Session.get('codeError') || code;
let formatedMessage = intl.formatMessage(intlMessages[defaultProps.code]);
- if (code in intlMessages) {
- formatedMessage = intl.formatMessage(intlMessages[code]);
+ if (codeError in intlMessages) {
+ formatedMessage = intl.formatMessage(intlMessages[codeError]);
}
-
return (
- {code}
+ {codeError}
{formatedMessage}
diff --git a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx
index d5e2d13c4b..57d1b3c7b8 100644
--- a/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx
@@ -6,7 +6,7 @@ import { setCustomLogoUrl } from '/imports/ui/components/user-list/service';
import { makeCall } from '/imports/ui/services/api';
import deviceInfo from '/imports/utils/deviceInfo';
import logger from '/imports/startup/client/logger';
-import LoadingScreen from '/imports/ui/components/loading-screen/component';
+import { withJoinLoadingConsumer } from '/imports/ui/components/join-loading/context/context';
const propTypes = {
children: PropTypes.element.isRequired,
@@ -17,14 +17,10 @@ const { showParticipantsOnLogin } = APP_CONFIG;
const CHAT_ENABLED = Meteor.settings.public.chat.enabled;
class JoinHandler extends Component {
- static setError(codeError) {
- Session.set('hasError', true);
- if (codeError) Session.set('codeError', codeError);
- }
-
constructor(props) {
super(props);
this.fetchToken = this.fetchToken.bind(this);
+ this.setError = this.setError.bind(this);
this.numFetchTokenRetries = 0;
this.state = {
@@ -41,6 +37,12 @@ class JoinHandler extends Component {
this._isMounted = false;
}
+ setError(codeError) {
+ const { dispatch } = this.props;
+ if (codeError) Session.set('codeError', codeError);
+ dispatch('hasError');
+ }
+
async fetchToken() {
if (!this._isMounted) return;
@@ -63,7 +65,7 @@ class JoinHandler extends Component {
const sessionToken = urlParams.get('sessionToken');
if (!sessionToken) {
- JoinHandler.setError('400');
+ this.setError('400');
Session.set('errorMessageDescription', 'Session token was not provided');
}
@@ -141,7 +143,6 @@ class JoinHandler extends Component {
const { response } = parseToJson;
setLogoutURL(response);
-
if (response.returncode !== 'FAILED') {
await setAuth(response);
@@ -167,9 +168,11 @@ class JoinHandler extends Component {
response,
},
}, 'User successfully went through main.joinRouteHandler');
+ this.setState({ joined: true });
} else {
const e = new Error(response.message);
if (!Session.get('codeError')) Session.set('errorMessageDescription', response.message);
+ this.setError(401);
logger.error({
logCode: 'joinhandler_component_joinroutehandler_error',
extraInfo: {
@@ -178,18 +181,15 @@ class JoinHandler extends Component {
},
}, 'User faced an error on main.joinRouteHandler.');
}
- this.setState({ joined: true });
}
render() {
const { children } = this.props;
const { joined } = this.state;
- return joined
- ? children
- : ();
+ return joined && children;
}
}
-export default JoinHandler;
+export default withJoinLoadingConsumer(JoinHandler);
JoinHandler.propTypes = propTypes;
diff --git a/bigbluebutton-html5/imports/ui/components/join-loading/component.jsx b/bigbluebutton-html5/imports/ui/components/join-loading/component.jsx
new file mode 100644
index 0000000000..c52f5c81b1
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/join-loading/component.jsx
@@ -0,0 +1,23 @@
+import React, { Fragment } from 'react';
+import LoadingScreen from '/imports/ui/components/loading-screen/component';
+import ErrorScreen from '/imports/ui/components/error-screen/component';
+import { withJoinLoadingContext } from './context/context';
+import IntlProvider from '/imports/startup/client/intl';
+import Settings from '/imports/ui/services/settings';
+
+const JoinLoadComponent = (props) => {
+ const { children, showLoading, hasError } = props;
+ const { locale } = Settings.application;
+ return (
+
+ {(showLoading && !hasError) && ()}
+ {hasError ? (
+
+
+
+ ) : children}
+
+ );
+};
+
+export default withJoinLoadingContext(JoinLoadComponent);
diff --git a/bigbluebutton-html5/imports/ui/components/join-loading/context/context.jsx b/bigbluebutton-html5/imports/ui/components/join-loading/context/context.jsx
new file mode 100644
index 0000000000..3c40b6c545
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/join-loading/context/context.jsx
@@ -0,0 +1,64 @@
+import React, { useReducer, createContext } from 'react';
+
+function reducer(state, action) {
+ switch (action) {
+ case 'showLoading':
+ return {
+ ...state,
+ showLoading: true,
+ };
+ case 'hideLoading':
+ return {
+ ...state,
+ showLoading: false,
+ };
+ case 'hasError':
+ return {
+ ...state,
+ hasError: true,
+ };
+ default:
+ throw new Error();
+ }
+}
+
+const JoinLoadingContext = createContext({});
+const initialState = {
+ showLoading: true,
+ hasError: false,
+};
+const JoinLoading = Component => (props) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const contextValue = {
+ ...props,
+ ...state,
+ dispatch,
+ };
+
+ return (
+
+
+
+ );
+};
+
+const joinLoadingContextConsumer = Component => props => (
+
+ { contexts => }
+
+);
+
+
+const withJoinLoadingConsumer = Component => joinLoadingContextConsumer(Component);
+
+const withJoinLoadingContext = Component => JoinLoading(withJoinLoadingConsumer(Component));
+
+export {
+ withJoinLoadingContext,
+ withJoinLoadingConsumer,
+ joinLoadingContextConsumer,
+ JoinLoading,
+ reducer,
+ initialState,
+ JoinLoadingContext,
+};
diff --git a/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss b/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss
index eeaf0ceade..0cfaa82333 100644
--- a/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss
+++ b/bigbluebutton-html5/imports/ui/components/loading-screen/styles.scss
@@ -11,13 +11,18 @@
}
.background {
- position: fixed;
display: flex;
flex-flow: column;
justify-content: center;
+ position: fixed;
width: 100%;
height: 100%;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
background-color: var(--loader-bg);
+ z-index: 99999;
}
.message {
diff --git a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx
index 55ccf33c9b..33e4d51a79 100755
--- a/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/subscriptions/component.jsx
@@ -8,6 +8,7 @@ import Annotations from '/imports/api/annotations';
import AnnotationsTextService from '/imports/ui/components/whiteboard/annotations/text/service';
import AnnotationsLocal from '/imports/ui/components/whiteboard/service';
import mapUser from '/imports/ui/services/user/mapUser';
+import { withJoinLoadingConsumer } from '/imports/ui/components/join-loading/context/context';
const CHAT_CONFIG = Meteor.settings.public.chat;
const CHAT_ENABLED = CHAT_CONFIG.enabled;
@@ -34,7 +35,7 @@ class Subscriptions extends Component {
}
}
-export default withTracker(() => {
+export default withJoinLoadingConsumer(withTracker(({ dispatch }) => {
const { credentials } = Auth;
const { meetingId, requesterUserId } = credentials;
if (Session.get('codeError')) {
@@ -50,6 +51,7 @@ export default withTracker(() => {
extraInfo: { error },
}, 'Error while subscribing to collections');
Session.set('codeError', error.error);
+ dispatch('hasError');
},
};
@@ -115,4 +117,4 @@ export default withTracker(() => {
subscriptionsReady: ready,
subscriptionsHandlers,
};
-})(Subscriptions);
+})(Subscriptions));