Merge branch 'v2.6.x-release' into capture-shared-notes
This commit is contained in:
commit
184b1c2169
@ -12,6 +12,7 @@ import userActivitySign from './methods/userActivitySign';
|
||||
import userLeftMeeting from './methods/userLeftMeeting';
|
||||
import changePin from './methods/changePin';
|
||||
import setRandomUser from './methods/setRandomUser';
|
||||
import setExitReason from './methods/setExitReason';
|
||||
|
||||
Meteor.methods({
|
||||
setEmojiStatus,
|
||||
@ -27,4 +28,5 @@ Meteor.methods({
|
||||
userLeftMeeting,
|
||||
changePin,
|
||||
setRandomUser,
|
||||
setExitReason,
|
||||
});
|
||||
|
@ -0,0 +1,21 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import setUserExitReason from '/imports/api/users/server/modifiers/setUserExitReason';
|
||||
|
||||
export default function setExitReason(reason) {
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
// Unauthenticated user, just ignore and go ahead.
|
||||
if (!meetingId || !requesterUserId) return;
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(reason, String);
|
||||
|
||||
setUserExitReason(meetingId, requesterUserId, reason);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method setExitReason ${err.stack}`);
|
||||
}
|
||||
};
|
@ -45,7 +45,20 @@ export default function userLeaving(meetingId, userId, connectionId) {
|
||||
|
||||
ClientConnections.removeClientConnection(`${meetingId}--${userId}`, connectionId);
|
||||
|
||||
Logger.info(`User '${userId}' is leaving meeting '${meetingId}'`);
|
||||
let reason;
|
||||
|
||||
if (user.loggedOut) {
|
||||
// User explicitly requested logout.
|
||||
reason = 'logout';
|
||||
} else if (user.exitReason) {
|
||||
// User didn't requested logout but exited graciously.
|
||||
reason = user.exitReason;
|
||||
} else {
|
||||
// User didn't exit graciously (disconnection).
|
||||
reason = 'disconnection';
|
||||
}
|
||||
|
||||
Logger.info(`User '${userId}' is leaving meeting '${meetingId}' reason=${reason}`);
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method userLeaving ${err.stack}`);
|
||||
|
@ -0,0 +1,25 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
|
||||
export default function setUserExitReason(meetingId, userId, reason) {
|
||||
const selector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
exitReason: reason,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const numberAffected = Users.update(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.info(`Set exit reason userId=${userId} meeting=${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Setting user exit reason: ${err}`);
|
||||
}
|
||||
};
|
@ -20,6 +20,7 @@ import VideoService from '/imports/ui/components/video-provider/service';
|
||||
import DebugWindow from '/imports/ui/components/debug-window/component';
|
||||
import { ACTIONS, PANELS } from '../../ui/components/layout/enums';
|
||||
import { isChatEnabled } from '/imports/ui/services/features';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
|
||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||
const PUBLIC_CHAT_ID = CHAT_CONFIG.public_id;
|
||||
@ -238,6 +239,10 @@ class Base extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
static async setExitReason(reason) {
|
||||
return await makeCall('setExitReason', reason);
|
||||
}
|
||||
|
||||
renderByState() {
|
||||
const { updateLoadingState } = this;
|
||||
const stateControls = { updateLoadingState };
|
||||
@ -259,35 +264,49 @@ class Base extends Component {
|
||||
}
|
||||
|
||||
if (meetingIsBreakout && (ejected || userRemoved)) {
|
||||
window.close();
|
||||
Base.setExitReason('removedFromBreakout').finally(() => {
|
||||
Meteor.disconnect();
|
||||
window.close();
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ejected) {
|
||||
return (<MeetingEnded code="403" ejectedReason={ejectedReason} />);
|
||||
return (
|
||||
<MeetingEnded
|
||||
code="403"
|
||||
ejectedReason={ejectedReason}
|
||||
callback={() => Base.setExitReason('ejected')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if ((meetingHasEnded || User?.loggedOut) && meetingIsBreakout) {
|
||||
window.close();
|
||||
const reason = meetingHasEnded ? 'breakoutEnded' : 'logout';
|
||||
Base.setExitReason(reason).finally(() => {
|
||||
Meteor.disconnect();
|
||||
window.close();
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
if (((meetingHasEnded && !meetingIsBreakout)) || (codeError && User?.loggedOut)) {
|
||||
if ((meetingHasEnded && !meetingIsBreakout) || (codeError && User?.loggedOut)) {
|
||||
return (
|
||||
<MeetingEnded
|
||||
code={codeError}
|
||||
endedReason={meetingEndedReason}
|
||||
ejectedReason={ejectedReason}
|
||||
callback={() => Base.setExitReason('meetingEnded')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (codeError && !meetingHasEnded) {
|
||||
// 680 is set for the codeError when the user requests a logout
|
||||
// 680 is set for the codeError when the user requests a logout.
|
||||
if (codeError !== '680') {
|
||||
return (<ErrorScreen code={codeError} />);
|
||||
return (<ErrorScreen code={codeError} callback={() => Base.setExitReason('error')} />);
|
||||
}
|
||||
return (<MeetingEnded code={codeError} />);
|
||||
return (<MeetingEnded code={codeError} callback={() => Base.setExitReason('logout')} />);
|
||||
}
|
||||
|
||||
return (<AppContainer {...this.props} baseControls={stateControls} />);
|
||||
@ -378,7 +397,7 @@ export default withTracker(() => {
|
||||
const connectionIdUpdateTime = User?.connectionIdUpdateTime;
|
||||
|
||||
if (currentConnectionId && currentConnectionId !== connectionID && connectionIdUpdateTime > connectionAuthTime) {
|
||||
Session.set('codeError', 403);
|
||||
Session.set('codeError', '409');
|
||||
Session.set('errorMessageDescription', 'joined_another_window_reason')
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,16 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
|
||||
setTimeout(() => {
|
||||
const queryCurrentUser = Users.find({ userId: Auth.userID, meetingId: Auth.meetingID });
|
||||
if (queryCurrentUser.count() === 0) {
|
||||
endMeeting(403, userData.ejectedReason || null);
|
||||
if (userData.ejected) {
|
||||
endMeeting('403', userData.ejectedReason);
|
||||
} else {
|
||||
// Either authentication process hasn't finished yet or user did authenticate but Users
|
||||
// collection is unsynchronized. In both cases user may be able to rejoin.
|
||||
const description = Auth.isAuthenticating || Auth.loggedIn
|
||||
? 'able_to_rejoin_user_disconnected_reason'
|
||||
: null;
|
||||
endMeeting('503', description);
|
||||
}
|
||||
}
|
||||
}, delayForReconnection);
|
||||
},
|
||||
|
@ -8,6 +8,9 @@ import logger from '/imports/startup/client/logger';
|
||||
import Styled from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
503: {
|
||||
id: 'app.error.503',
|
||||
},
|
||||
500: {
|
||||
id: 'app.error.500',
|
||||
defaultMessage: 'Oops, something went wrong',
|
||||
@ -15,6 +18,9 @@ const intlMessages = defineMessages({
|
||||
410: {
|
||||
id: 'app.error.410',
|
||||
},
|
||||
409: {
|
||||
id: 'app.error.409',
|
||||
},
|
||||
408: {
|
||||
id: 'app.error.408',
|
||||
},
|
||||
@ -55,6 +61,9 @@ const intlMessages = defineMessages({
|
||||
not_enough_permission_eject_reason: {
|
||||
id: 'app.meeting.logout.permissionEjectReason',
|
||||
},
|
||||
able_to_rejoin_user_disconnected_reason: {
|
||||
id: 'app.error.disconnected.rejoin',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
@ -66,14 +75,17 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
code: 500,
|
||||
callback: async () => {},
|
||||
};
|
||||
|
||||
class ErrorScreen extends PureComponent {
|
||||
componentDidMount() {
|
||||
const { code } = this.props;
|
||||
const { code, callback } = this.props;
|
||||
const log = code === 403 ? 'warn' : 'error';
|
||||
AudioManager.exitAudio();
|
||||
Meteor.disconnect();
|
||||
callback().finally(() => {
|
||||
Meteor.disconnect();
|
||||
});
|
||||
logger[log]({ logCode: 'startup_client_usercouldnotlogin_error' }, `User could not log in HTML5, hit ${code}`);
|
||||
}
|
||||
|
||||
@ -92,7 +104,7 @@ class ErrorScreen extends PureComponent {
|
||||
|
||||
let errorMessageDescription = Session.get('errorMessageDescription');
|
||||
|
||||
if (code === 403 && errorMessageDescription in intlMessages) {
|
||||
if (errorMessageDescription in intlMessages) {
|
||||
errorMessageDescription = intl.formatMessage(intlMessages[errorMessageDescription]);
|
||||
}
|
||||
|
||||
|
@ -116,6 +116,7 @@ const propTypes = {
|
||||
const defaultProps = {
|
||||
ejectedReason: null,
|
||||
endedReason: null,
|
||||
callback: async () => {},
|
||||
};
|
||||
|
||||
class MeetingEnded extends PureComponent {
|
||||
@ -159,7 +160,9 @@ class MeetingEnded extends PureComponent {
|
||||
AudioManager.exitAudio();
|
||||
Storage.removeItem('getEchoTest');
|
||||
Storage.removeItem('isFirstJoin');
|
||||
Meteor.disconnect();
|
||||
this.props.callback().finally(() => {
|
||||
Meteor.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedStar(starNumber) {
|
||||
|
@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import _ from 'lodash';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import Service from './service';
|
||||
import Styled from './styles';
|
||||
import { useState } from 'react';
|
||||
|
||||
const DEBOUNCE_TIMEOUT = 15000;
|
||||
const DEBOUNCE_OPTIONS = {
|
||||
leading: true,
|
||||
trailing: false,
|
||||
};
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
convertAndUploadLabel: {
|
||||
@ -28,15 +24,21 @@ const propTypes = {
|
||||
const ConverterButtonComponent = ({
|
||||
intl,
|
||||
amIPresenter,
|
||||
}) => (amIPresenter
|
||||
}) => {
|
||||
[converterButtonDisabled, setConverterButtonDisabled] = useState(false);
|
||||
return (amIPresenter
|
||||
? (
|
||||
<Styled.ConvertAndUpload
|
||||
onClick={_.debounce(() => Service.convertAndUpload(), DEBOUNCE_TIMEOUT, DEBOUNCE_OPTIONS)}
|
||||
disabled={converterButtonDisabled}
|
||||
onClick={() => {
|
||||
setConverterButtonDisabled(true);
|
||||
setTimeout(() => setConverterButtonDisabled(false), DEBOUNCE_TIMEOUT);
|
||||
return Service.convertAndUpload()}}
|
||||
label={intl.formatMessage(intlMessages.convertAndUploadLabel)}
|
||||
icon="upload"
|
||||
/>
|
||||
)
|
||||
: null);
|
||||
: null)};
|
||||
|
||||
ConverterButtonComponent.propTypes = propTypes;
|
||||
|
||||
|
@ -854,7 +854,7 @@ class Presentation extends PureComponent {
|
||||
const { downloadable } = currentPresentation;
|
||||
|
||||
return (
|
||||
<Styled.InnerToastWrapper>
|
||||
<Styled.InnerToastWrapper data-test="currentPresentationToast">
|
||||
<Styled.ToastIcon>
|
||||
<Styled.IconWrapper>
|
||||
<Icon iconName="presentation" />
|
||||
|
@ -428,12 +428,26 @@ class PresentationUploader extends Component {
|
||||
const selected = propPresentations.filter((p) => p.isCurrent);
|
||||
if (selected.length > 0) Session.set('selectedToBeNextCurrent', selected[0].id);
|
||||
}
|
||||
|
||||
if (this.exportToastId) {
|
||||
if (!prevProps.isOpen && isOpen) {
|
||||
this.handleDismissToast(this.exportToastId);
|
||||
}
|
||||
|
||||
toast.update(this.exportToastId, {
|
||||
render: this.renderExportToast(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Session.set('showUploadPresentationView', false);
|
||||
}
|
||||
|
||||
handleDismissToast() {
|
||||
return toast.dismiss(this.toastId);
|
||||
}
|
||||
|
||||
handleFiledrop(files, files2) {
|
||||
const { fileValidMimeTypes, intl } = this.props;
|
||||
const { toUploadCount } = this.state;
|
||||
|
@ -210,10 +210,15 @@ class Auth {
|
||||
}
|
||||
|
||||
this.loggedIn = false;
|
||||
this.isAuthenticating = true;
|
||||
|
||||
return this.validateAuthToken()
|
||||
.then(() => {
|
||||
this.loggedIn = true;
|
||||
this.uniqueClientSession = `${this.sessionToken}-${Math.random().toString(36).substring(6)}`;
|
||||
})
|
||||
.finally(() => {
|
||||
this.isAuthenticating = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -664,8 +664,11 @@
|
||||
"app.error.403": "You have been removed from the meeting",
|
||||
"app.error.404": "Not found",
|
||||
"app.error.408": "Authentication failed",
|
||||
"app.error.409": "Conflict",
|
||||
"app.error.410": "Meeting has ended",
|
||||
"app.error.500": "Ops, something went wrong",
|
||||
"app.error.503": "You have been disconnected",
|
||||
"app.error.disconnected.rejoin": "You are able to refresh the page to rejoin.",
|
||||
"app.error.userLoggedOut": "User has an invalid sessionToken due to log out",
|
||||
"app.error.ejectedUser": "User has an invalid sessionToken due to ejection",
|
||||
"app.error.joinedAnotherWindow": "This session seems to be opened in another browser window.",
|
||||
|
Loading…
Reference in New Issue
Block a user