bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/join-handler/component.jsx

268 lines
8.1 KiB
React
Raw Normal View History

import React, { Component } from 'react';
2018-11-20 00:53:25 +08:00
import { Session } from 'meteor/session';
2018-12-18 23:15:51 +08:00
import PropTypes from 'prop-types';
import SanitizeHTML from 'sanitize-html';
import Auth from '/imports/ui/services/auth';
import { setCustomLogoUrl, setModeratorOnlyMessage } from '/imports/ui/components/user-list/service';
import { makeCall } from '/imports/ui/services/api';
import logger from '/imports/startup/client/logger';
import LoadingScreen from '/imports/ui/components/common/loading-screen/component';
import { CurrentUser } from '/imports/api/users';
import Settings from '/imports/ui/services/settings';
import { updateSettings } from '/imports/ui/components/settings/service';
import { LAYOUT_TYPE } from '/imports/ui/components/layout/enums';
2018-12-18 23:15:51 +08:00
const propTypes = {
children: PropTypes.element.isRequired,
};
2018-11-16 23:58:49 +08:00
class JoinHandler extends Component {
static setError(codeError) {
if (codeError) Session.set('codeError', codeError);
}
constructor(props) {
super(props);
2018-11-19 23:40:42 +08:00
this.fetchToken = this.fetchToken.bind(this);
this.state = {
joined: false,
hasAlreadyJoined: false,
};
}
componentDidMount() {
2019-07-13 03:22:47 +08:00
this._isMounted = true;
if (!this.firstJoinTime) {
this.firstJoinTime = new Date();
}
Tracker.autorun((c) => {
const {
connected,
status,
} = Meteor.status();
const { hasAlreadyJoined } = this.state;
if (status === 'connecting' && !hasAlreadyJoined) {
this.setState({ joined: false });
}
logger.debug(`Initial connection status change. status: ${status}, connected: ${connected}`);
if (connected) {
const msToConnect = (new Date() - this.firstJoinTime) / 1000;
const secondsToConnect = parseFloat(msToConnect).toFixed(2);
logger.info({
logCode: 'joinhandler_component_initial_connection_time',
extraInfo: {
attemptForUserInfo: Auth.fullInfo,
timeToConnect: secondsToConnect,
},
}, `Connection to Meteor took ${secondsToConnect}s`);
this.firstJoinTime = undefined;
this.fetchToken();
} else if (status === 'failed') {
c.stop();
const msToConnect = (new Date() - this.firstJoinTime) / 1000;
const secondsToConnect = parseFloat(msToConnect).toFixed(2);
logger.info({
logCode: 'joinhandler_component_initial_connection_failed',
extraInfo: {
attemptForUserInfo: Auth.fullInfo,
timeToConnect: secondsToConnect,
},
}, `Connection to Meteor failed, took ${secondsToConnect}s`);
JoinHandler.setError('400');
Session.set('errorMessageDescription', 'Failed to connect to server');
this.firstJoinTime = undefined;
}
});
}
2019-07-13 03:22:47 +08:00
componentWillUnmount() {
this._isMounted = false;
}
2018-12-10 23:52:25 +08:00
async fetchToken() {
const { hasAlreadyJoined } = this.state;
Allow BBB to run behind a proxy the avoid gUM permission queries per node The idea is to run a loadbalancer node which maps each BBB node to a path. That way each user gets only one gUM permission query for a cluster. The loadbalancer node only serves the html5 client, each BBB node will serve its own API and handle the websockets for freeswitch and bbb-webrtc-sfu. Configuring a cluster setup =========================== * let bbb-lb.example.com be the loadbalancer node * let bbb-01.eaxmple.com be a BBB node Loadbalancer ------------ On the loadbalancer node add an nginx configuration similar to this one for each BBB node: ``` location /bbb-01/html5client/ { proxy_pass https://bbb-01.example.com/bbb-01/html5client/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } ``` BBB Node -------- On the BBB node add the following options to `/etc/bigbluebutton/bbb-web.properties`: ``` defaultHTML5ClientUrl=https://bbb-lb.example.com/bbb-01/html5client/join presentationBaseURL=https://bbb-01.example.com/bigbluebutton/presentation accessControlAllowOrigin=https://bbb-lb.example.com ``` Add the following options to `/etc/bigbluebutton/bbb-html5.yml`: ``` public: app: basename: '/bbb-01/html5client' bbbWebBase: 'https://bbb-01.eaxmple.com/bigbluebutton' learningDashboardBase: 'https://bbb-01.eaxmple.com/learning-dashboard' media: stunTurnServersFetchAddress: 'https://bbb-01.eaxmple.com/bigbluebutton/api/stuns' sip_ws_host: 'bbb-01.eaxmple.com' presentation: uploadEndpoint: 'https://bbb-01.eaxmple.com/bigbluebutton/presentation/upload' ``` Create the following unit file overrides: * `/etc/systemd/system/bbb-html5-frontend@.service.d/cluster.conf` * `/etc/systemd/system/bbb-html5-backend@.service.d/cluster.conf` with the following content: ``` [Service] Environment=ROOT_URL=https://127.0.0.1/bbb-01/html5client ``` Change the nginx `$bbb_loadbalancer_node` variable to the name of the load balancer node in `/etc/bigbluebutton/nginx/loadbalancer.nginx` to allow CORS requests: ``` set $bbb_loadbalancer_node https://bbb-lb.example.com ``` Prepend the mount point of bbb-html5 in all location sections except from the `location @html5client` section in `/etc/bigbluebutton/nginx/bbb-html5.nginx` ``` location @html5client { ... } location /bbb-01/html5client/locales { ... } ```
2021-11-19 05:52:20 +08:00
const APP = Meteor.settings.public.app;
2019-07-13 03:22:47 +08:00
if (!this._isMounted) return;
const urlParams = new URLSearchParams(window.location.search);
const sessionToken = urlParams.get('sessionToken');
if (!sessionToken) {
JoinHandler.setError('400');
Session.set('errorMessageDescription', 'Session token was not provided');
}
// Old credentials stored in memory were being used when joining a new meeting
if (!hasAlreadyJoined) {
Auth.clearCredentials();
}
const logUserInfo = () => {
const userInfo = window.navigator;
// Browser information is sent once on startup
// Sent here instead of Meteor.startup, as the
// user might not be validated by then, thus user's data
// would not be sent with this information
const clientInfo = {
language: userInfo.language,
userAgent: userInfo.userAgent,
screenSize: { width: window.screen.width, height: window.screen.height },
windowSize: { width: window.innerWidth, height: window.innerHeight },
bbbVersion: Meteor.settings.public.app.bbbServerVersion,
location: window.location.href,
};
logger.info({
logCode: 'joinhandler_component_clientinfo',
extraInfo: { clientInfo },
},
'Log information about the client');
};
const setAuth = (resp) => {
const {
meetingID, internalUserID, authToken, logoutUrl,
fullname, externUserID, confname,
} = resp;
2018-12-10 23:52:25 +08:00
return new Promise((resolve) => {
Auth.set(
meetingID, internalUserID, authToken, logoutUrl,
sessionToken, fullname, externUserID, confname,
);
resolve(resp);
});
};
2018-11-30 06:37:51 +08:00
const setLogoutURL = (url) => {
Auth.logoutURL = url;
return true;
};
2018-11-19 23:40:42 +08:00
const setLogoURL = (resp) => {
setCustomLogoUrl(resp.customLogoURL);
return resp;
};
const setModOnlyMessage = (resp) => {
if (resp && resp.modOnlyMessage) {
const sanitizedModOnlyText = SanitizeHTML(resp.modOnlyMessage, {
allowedTags: ['a', 'b', 'br', 'i', 'img', 'li', 'small', 'span', 'strong', 'u', 'ul'],
allowedAttributes: {
a: ['href', 'name', 'target'],
img: ['src', 'width', 'height'],
},
allowedSchemes: ['https'],
});
setModeratorOnlyMessage(sanitizedModOnlyText);
}
return resp;
};
const setCustomData = (resp) => {
2020-04-29 12:41:16 +08:00
const { customdata } = resp;
2018-12-10 23:52:25 +08:00
return new Promise((resolve) => {
if (customdata.length) {
makeCall('addUserSettings', customdata).then((r) => resolve(r));
2018-12-10 23:52:25 +08:00
}
resolve(true);
});
};
2019-03-16 04:07:14 +08:00
const setBannerProps = (resp) => {
Session.set('bannerText', resp.bannerText);
Session.set('bannerColor', resp.bannerColor);
};
const setUserDefaultLayout = (response) => {
const settings = {
application: {
...Settings.application,
selectedLayout: LAYOUT_TYPE[response.defaultLayout] || 'custom',
},
};
updateSettings(settings);
};
// use enter api to get params for the client
Allow BBB to run behind a proxy the avoid gUM permission queries per node The idea is to run a loadbalancer node which maps each BBB node to a path. That way each user gets only one gUM permission query for a cluster. The loadbalancer node only serves the html5 client, each BBB node will serve its own API and handle the websockets for freeswitch and bbb-webrtc-sfu. Configuring a cluster setup =========================== * let bbb-lb.example.com be the loadbalancer node * let bbb-01.eaxmple.com be a BBB node Loadbalancer ------------ On the loadbalancer node add an nginx configuration similar to this one for each BBB node: ``` location /bbb-01/html5client/ { proxy_pass https://bbb-01.example.com/bbb-01/html5client/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } ``` BBB Node -------- On the BBB node add the following options to `/etc/bigbluebutton/bbb-web.properties`: ``` defaultHTML5ClientUrl=https://bbb-lb.example.com/bbb-01/html5client/join presentationBaseURL=https://bbb-01.example.com/bigbluebutton/presentation accessControlAllowOrigin=https://bbb-lb.example.com ``` Add the following options to `/etc/bigbluebutton/bbb-html5.yml`: ``` public: app: basename: '/bbb-01/html5client' bbbWebBase: 'https://bbb-01.eaxmple.com/bigbluebutton' learningDashboardBase: 'https://bbb-01.eaxmple.com/learning-dashboard' media: stunTurnServersFetchAddress: 'https://bbb-01.eaxmple.com/bigbluebutton/api/stuns' sip_ws_host: 'bbb-01.eaxmple.com' presentation: uploadEndpoint: 'https://bbb-01.eaxmple.com/bigbluebutton/presentation/upload' ``` Create the following unit file overrides: * `/etc/systemd/system/bbb-html5-frontend@.service.d/cluster.conf` * `/etc/systemd/system/bbb-html5-backend@.service.d/cluster.conf` with the following content: ``` [Service] Environment=ROOT_URL=https://127.0.0.1/bbb-01/html5client ``` Change the nginx `$bbb_loadbalancer_node` variable to the name of the load balancer node in `/etc/bigbluebutton/nginx/loadbalancer.nginx` to allow CORS requests: ``` set $bbb_loadbalancer_node https://bbb-lb.example.com ``` Prepend the mount point of bbb-html5 in all location sections except from the `location @html5client` section in `/etc/bigbluebutton/nginx/bbb-html5.nginx` ``` location @html5client { ... } location /bbb-01/html5client/locales { ... } ```
2021-11-19 05:52:20 +08:00
const url = `${APP.bbbWebBase}/api/enter?sessionToken=${sessionToken}`;
const fetchContent = await fetch(url, { credentials: 'include' });
2018-12-10 23:52:25 +08:00
const parseToJson = await fetchContent.json();
const { response } = parseToJson;
2019-01-08 00:26:23 +08:00
2023-09-13 20:37:58 +08:00
setLogoutURL(response.logoutUrl);
logUserInfo();
2018-12-10 23:52:25 +08:00
if (response.returncode !== 'FAILED') {
await setAuth(response);
2019-03-16 04:07:14 +08:00
setBannerProps(response);
2018-12-10 23:52:25 +08:00
setLogoURL(response);
setModOnlyMessage(response);
2020-05-05 09:16:52 +08:00
Tracker.autorun(async (cd) => {
const user = CurrentUser
.findOne({ userId: Auth.userID, approved: true }, { fields: { _id: 1 } });
2020-05-05 09:16:52 +08:00
if (user) {
await setCustomData(response);
setUserDefaultLayout(response);
2020-05-05 09:16:52 +08:00
cd.stop();
}
});
logger.info({
logCode: 'joinhandler_component_joinroutehandler_success',
extraInfo: {
response,
},
}, 'User successfully went through main.joinRouteHandler');
2018-12-10 23:52:25 +08:00
} else {
if(['missingSession','meetingForciblyEnded','notFound'].includes(response.messageKey)) {
JoinHandler.setError('410');
Session.set('errorMessageDescription', 'meeting_ended');
} else if(response.messageKey == "guestDeny") {
JoinHandler.setError('401');
Session.set('errorMessageDescription', 'guest_deny');
} else if(response.messageKey == "maxParticipantsReached") {
JoinHandler.setError('401');
Session.set('errorMessageDescription', 'max_participants_reason');
} else {
JoinHandler.setError('401');
Session.set('errorMessageDescription', response.message);
}
logger.error({
logCode: 'joinhandler_component_joinroutehandler_error',
extraInfo: {
response,
error: new Error(response.message),
},
}, 'User faced an error on main.joinRouteHandler.');
2018-12-10 23:52:25 +08:00
}
this.setState({
joined: true,
hasAlreadyJoined: true,
});
}
render() {
const { children } = this.props;
const { joined } = this.state;
return joined
? children
: (<LoadingScreen />);
}
}
export default JoinHandler;
2018-12-18 23:15:51 +08:00
JoinHandler.propTypes = propTypes;