Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lang/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,7 @@
"desktopShareFramerate": "Desktop sharing frame rate",
"desktopShareHighFpsWarning": "A higher frame rate for desktop sharing might affect your bandwidth. You need to restart the screen share for the new settings to take effect.",
"desktopShareWarning": "You need to restart the screen share for the new settings to take effect.",
"enableBeforeUnloadConfirmation": "Warn before leaving a meeting",
"devices": "Devices",
"followMe": "Everyone follows me",
"followMeRecorder": "Recorder follows me",
Expand Down
2 changes: 1 addition & 1 deletion react/features/always-on-top/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ import AlwaysOnTop from './AlwaysOnTop';
ReactDOM.render(<AlwaysOnTop />, document.getElementById('react'));

window.addEventListener(
'beforeunload',
'unload',
/* eslint-disable-next-line react/no-deprecated */
() => ReactDOM.unmountComponentAtNode(document.getElementById('react') ?? document.body));
66 changes: 53 additions & 13 deletions react/features/base/conference/middleware.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ import { IConferenceMetadata } from './reducer';
*/
let beforeUnloadHandler: ((e?: any) => void) | undefined;

/**
* Handler for unload event.
*/
let unloadHandler: (() => void) | undefined;

/**
* A simple flag to avoid retrying more than once to join as a visitor when hitting max occupants reached.
*/
Expand Down Expand Up @@ -329,7 +334,6 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
const { conference } = action;
const { pendingSubjectChange } = getState()['features/base/conference'];
const {
disableBeforeUnloadHandlers = false,
requireDisplayName
} = getState()['features/base/config'];

Expand All @@ -345,13 +349,34 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
// that should cover the described use case as part of the effort to
// implement the conferenceWillLeave action for web.
beforeUnloadHandler = (e?: any) => {
const isBeforeUnload = e?.type === 'beforeunload';

if (isBeforeUnload) {
// Trigger the browser's "Are you sure?" prompt.
e.preventDefault();

// Most modern browsers will ignore this string and show their own,
// but we'll set it anyway for older browsers or better compatibility.
e.returnValue = 'You are in an active meeting. Do you really want to leave?';

// We MUST return here and NOT clean up the conference state yet.
// If we clean it up now, the app might break even if the user stays.
return;
}
};

/**
* Handles the actual unloading of the page.
*
* @returns {void}
*/
unloadHandler = () => {
// Stop recording only when we are sure the user is leaving.
// Doing this in 'unload' avoids freezing the 'beforeunload' prompt.
if (LocalRecordingManager.isRecordingLocally()) {
dispatch(stopLocalVideoRecording());
if (e) {
e.preventDefault();
e.returnValue = null;
}
}

dispatch(conferenceWillLeave(conference));
};

Expand All @@ -361,10 +386,18 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
dispatch(overwriteConfig({ disableFocus: false }));
}

window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);
const { enableBeforeUnloadConfirmation: configEnabled } = getState()['features/base/config'];
const { enableBeforeUnloadConfirmation: settingEnabled } = getState()['features/base/settings'];

if (typeof window !== 'undefined') {
if (configEnabled || settingEnabled) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should also check for undefined, which is falsey, but you likely want the user setting to take precedence, if set.

window.addEventListener('beforeunload', beforeUnloadHandler);
}
window.addEventListener('unload', unloadHandler);
}

if (requireDisplayName
&& !getLocalParticipant(getState)?.name
&& !getLocalParticipant(getState())?.name
&& !conference.isHidden()) {
dispatch(openDisplayNamePrompt({
validateInput: hasDisplayName
Expand Down Expand Up @@ -618,16 +651,23 @@ function _pinParticipant({ getState }: IStore, next: Function, action: AnyAction
/**
* Removes the unload handler.
*
* @param {Function} getState - The redux getState function.
* @param {Function} _getState - The redux getState function.
* @returns {void}
*/
function _removeUnloadHandler(getState: IStore['getState']) {
if (typeof beforeUnloadHandler !== 'undefined') {
const { disableBeforeUnloadHandlers = false } = getState()['features/base/config'];
function _removeUnloadHandler(_getState: IStore['getState']) {
if (typeof window === 'undefined') {
return;
}

window.removeEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);
if (typeof beforeUnloadHandler !== 'undefined') {
window.removeEventListener('beforeunload', beforeUnloadHandler);
beforeUnloadHandler = undefined;
}

if (typeof unloadHandler !== 'undefined') {
window.removeEventListener('unload', unloadHandler);
unloadHandler = undefined;
}
}

/**
Expand Down Expand Up @@ -753,7 +793,7 @@ function _updateLocalParticipantInConference({ dispatch, getState }: IStore, nex
const { participant } = action;
const result = next(action);

const localParticipant = getLocalParticipant(getState);
const localParticipant = getLocalParticipant(getState());

if (conference && participant.id === localParticipant?.id) {
if ('name' in participant) {
Expand Down
1 change: 1 addition & 0 deletions react/features/base/config/configType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ export interface IConfig {
maxMessagesPerSecond?: number;
numRequests?: number;
};
enableBeforeUnloadConfirmation?: boolean;
enableCalendarIntegration?: boolean;
enableClosePage?: boolean;
enableDisplayNameInStats?: boolean;
Expand Down
1 change: 1 addition & 0 deletions react/features/base/config/configWhitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export default [
'e2eeLabels',
'e2ee',
'e2eping',
'enableBeforeUnloadConfirmation',
'enableCalendarIntegration',
'enableDisplayNameInStats',
'enableEmailInStats',
Expand Down
2 changes: 2 additions & 0 deletions react/features/base/settings/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const DEFAULT_STATE: ISettingsState = {
userSelectedNotifications: {
'notify.chatMessages': true
},
enableBeforeUnloadConfirmation: false,
userSelectedMicDeviceLabel: undefined
};

Expand All @@ -69,6 +70,7 @@ export interface ISettingsState {
disableSelfView?: boolean;
displayName?: string;
email?: string;
enableBeforeUnloadConfirmation?: boolean;
hideShareAudioHelper?: boolean;
localFlipX?: boolean;
maxStageParticipants?: number;
Expand Down
5 changes: 1 addition & 4 deletions react/features/external-api/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,13 +238,10 @@ MiddlewareRegistry.register(store => next => action => {
break;

case SET_CONFIG: {
const state = store.getState();
const { disableBeforeUnloadHandlers = false } = state['features/base/config'];

/**
* Disposing the API when the user closes the page.
*/
window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', () => {
window.addEventListener('unload', () => {
APP.API.notifyConferenceLeft(APP.conference.roomName);
APP.API.dispose();
getJitsiMeetTransport().dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ document.addEventListener('DOMContentLoaded', () => {
);
});

window.addEventListener('beforeunload', () => {
window.addEventListener('unload', () => {
/* eslint-disable-next-line react/no-deprecated */
ReactDOM.unmountComponentAtNode(document.getElementById('react')!);
});
4 changes: 4 additions & 0 deletions react/features/settings/actions.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ export function submitMoreTab(newState: any) {
if (newState.showSubtitlesOnStage !== currentState.showSubtitlesOnStage) {
dispatch(updateSettings({ showSubtitlesOnStage: newState.showSubtitlesOnStage }));
}

if (newState.enableBeforeUnloadConfirmation !== currentState.enableBeforeUnloadConfirmation) {
dispatch(updateSettings({ enableBeforeUnloadConfirmation: newState.enableBeforeUnloadConfirmation }));
}
};
}

Expand Down
24 changes: 24 additions & 0 deletions react/features/settings/components/web/MoreTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export interface IProps extends AbstractDialogTabProps, WithTranslation {
*/
disableHideSelfView: boolean;

/**
* Whether or not the beforeunload confirmation is enabled.
*/
enableBeforeUnloadConfirmation: boolean;

/**
* Whether or not follow me is currently active (enabled by some other participant).
*/
Expand Down Expand Up @@ -126,6 +131,7 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
this._onMaxStageParticipantsSelect = this._onMaxStageParticipantsSelect.bind(this);
this._onHideSelfViewChanged = this._onHideSelfViewChanged.bind(this);
this._onShowSubtitlesOnStageChanged = this._onShowSubtitlesOnStageChanged.bind(this);
this._onEnableBeforeUnloadConfirmationChanged = this._onEnableBeforeUnloadConfirmationChanged.bind(this);
this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this);
}

Expand All @@ -141,6 +147,7 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
disableHideSelfView,
iAmVisitor,
hideSelfView,
enableBeforeUnloadConfirmation,
showLanguageSettings,
showSubtitlesOnStage,
t
Expand All @@ -166,6 +173,12 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
label = { t('settings.showSubtitlesOnStage') }
name = 'show-subtitles-button'
onChange = { this._onShowSubtitlesOnStageChanged } /> }
<Checkbox
checked = { enableBeforeUnloadConfirmation }
className = { classes.checkbox }
label = { t('settings.enableBeforeUnloadConfirmation') }
name = 'enable-before-unload-confirmation'
onChange = { this._onEnableBeforeUnloadConfirmationChanged } />
{showLanguageSettings && this._renderLanguageSelect()}
</div>
);
Expand Down Expand Up @@ -206,6 +219,17 @@ class MoreTab extends AbstractDialogTab<IProps, any> {
super._onChange({ showSubtitlesOnStage: checked });
}

/**
* Callback invoked to select if beforeunload confirmation should be enabled.
*
* @param {Object} e - The key event to handle.
*
* @returns {void}
*/
_onEnableBeforeUnloadConfirmationChanged({ target: { checked } }: React.ChangeEvent<HTMLInputElement>) {
super._onChange({ enableBeforeUnloadConfirmation: checked });
}

/**
* Callback invoked to select a language from select dropdown.
*
Expand Down
1 change: 1 addition & 0 deletions react/features/settings/components/web/SettingsDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
...newProps,
currentLanguage: tabState?.currentLanguage,
hideSelfView: tabState?.hideSelfView,
enableBeforeUnloadConfirmation: tabState?.enableBeforeUnloadConfirmation,
showSubtitlesOnStage: tabState?.showSubtitlesOnStage,
maxStageParticipants: tabState?.maxStageParticipants
};
Expand Down
1 change: 1 addition & 0 deletions react/features/settings/functions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export function getMoreTabProps(stateful: IStateful) {
areClosedCaptionsEnabled: areClosedCaptionsEnabled(state),
currentLanguage: language,
disableHideSelfView: disableSelfViewSettings || disableSelfView,
enableBeforeUnloadConfirmation: state['features/base/settings'].enableBeforeUnloadConfirmation,
hideSelfView: getHideSelfView(state),
iAmVisitor: iAmVisitor(state),
languages: LANGUAGES,
Expand Down
9 changes: 3 additions & 6 deletions react/features/visitors/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,8 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {

// let's subscribe for visitor waiting queue
const { room } = getState()['features/base/conference'];
const { disableBeforeUnloadHandlers = false } = getState()['features/base/config'];
const conferenceJid = `${room}@${hosts?.muc}`;
const beforeUnloadHandler = () => {
const unloadHandler = () => {
WebsocketClient.getInstance().disconnect();
};

Expand All @@ -194,9 +193,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {

WebsocketClient.getInstance().disconnect()
.then(() => {
window.removeEventListener(
disableBeforeUnloadHandlers ? 'unload' : 'beforeunload',
beforeUnloadHandler);
window.removeEventListener('unload', unloadHandler);
let delay = 0;

// now let's connect to meeting
Expand Down Expand Up @@ -226,7 +223,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
/**
* Disconnecting the WebSocket client when the user closes the page.
*/
window.addEventListener(disableBeforeUnloadHandlers ? 'unload' : 'beforeunload', beforeUnloadHandler);
window.addEventListener('unload', unloadHandler);


break;
Expand Down