Merge branch 'v2.6.x-release' into capture-shared-notes

This commit is contained in:
Daniel Petri Rocha 2022-10-08 11:24:22 +02:00
commit 184b1c2169
13 changed files with 151 additions and 23 deletions

View File

@ -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,
});

View File

@ -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}`);
}
};

View File

@ -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}`);

View File

@ -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}`);
}
};

View File

@ -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')
}

View File

@ -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);
},

View File

@ -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]);
}

View File

@ -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) {

View File

@ -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;

View File

@ -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" />

View File

@ -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;

View File

@ -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;
});
}

View File

@ -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.",