feat(conference): Enable forced reload of client on bridge failure.
* feat(conference): Enable forced reload of client on bridge failure. Force the client to reload when the bridge that is handling the media goes down. This mitigates issues seen on the bridge because of a client re-joining the call with the same endpointId, BWE issues, etc. This behavior is configurable through 'enableForcedReload' setting in config.js. The client skips the pre-join page when the page reloads. * squash: refactor the restart logic. * squash: fix description * squash: dispatch conferenceWillLeave action before reload.
This commit is contained in:
parent
16d88a288f
commit
0138f23755
|
@ -325,6 +325,11 @@ var config = {
|
||||||
// TCC sequence numbers starting from 0.
|
// TCC sequence numbers starting from 0.
|
||||||
// enableIceRestart: false,
|
// enableIceRestart: false,
|
||||||
|
|
||||||
|
// Enables forced reload of the client when the call is migrated as a result of
|
||||||
|
// the bridge going down. Currently enabled by default as call migration through
|
||||||
|
// session-terminate is causing siganling issues when Octo is enabled.
|
||||||
|
// enableForcedReload: true,
|
||||||
|
|
||||||
// Use TURN/UDP servers for the jitsi-videobridge connection (by default
|
// Use TURN/UDP servers for the jitsi-videobridge connection (by default
|
||||||
// we filter out TURN/UDP because it is usually not needed since the
|
// we filter out TURN/UDP because it is usually not needed since the
|
||||||
// bridge itself is reachable via UDP)
|
// bridge itself is reachable via UDP)
|
||||||
|
@ -732,6 +737,7 @@ var config = {
|
||||||
// 'dialog.reservationError',
|
// 'dialog.reservationError',
|
||||||
// 'dialog.serviceUnavailable', // shown when server is not reachable
|
// 'dialog.serviceUnavailable', // shown when server is not reachable
|
||||||
// 'dialog.sessTerminated', // shown when there is a failed conference session
|
// 'dialog.sessTerminated', // shown when there is a failed conference session
|
||||||
|
// 'dialog.sessionRestarted', // show when a client reload is initiated because of bridge migration
|
||||||
// 'dialog.tokenAuthFailed', // show when an invalid jwt is used
|
// 'dialog.tokenAuthFailed', // show when an invalid jwt is used
|
||||||
// 'dialog.transcribing', // transcribing notifications (pending, off)
|
// 'dialog.transcribing', // transcribing notifications (pending, off)
|
||||||
// 'dialOut.statusMessage', // shown when dial out status is updated.
|
// 'dialOut.statusMessage', // shown when dial out status is updated.
|
||||||
|
|
|
@ -280,6 +280,7 @@
|
||||||
"sendPrivateMessageTitle": "Send privately?",
|
"sendPrivateMessageTitle": "Send privately?",
|
||||||
"serviceUnavailable": "Service unavailable",
|
"serviceUnavailable": "Service unavailable",
|
||||||
"sessTerminated": "Call terminated",
|
"sessTerminated": "Call terminated",
|
||||||
|
"sessionRestarted": "Call restarted by the bridge",
|
||||||
"Share": "Share",
|
"Share": "Share",
|
||||||
"shareVideoLinkError": "Please provide a correct youtube link.",
|
"shareVideoLinkError": "Please provide a correct youtube link.",
|
||||||
"shareVideoTitle": "Share a video",
|
"shareVideoTitle": "Share a video",
|
||||||
|
|
|
@ -42,16 +42,6 @@ export const CONFERENCE_JOINED = 'CONFERENCE_JOINED';
|
||||||
*/
|
*/
|
||||||
export const CONFERENCE_LEFT = 'CONFERENCE_LEFT';
|
export const CONFERENCE_LEFT = 'CONFERENCE_LEFT';
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of (redux) action which signals that an uuid for a conference has been set.
|
|
||||||
*
|
|
||||||
* {
|
|
||||||
* type: CONFERENCE_UNIQUE_ID_SET,
|
|
||||||
* conference: JitsiConference
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export const CONFERENCE_UNIQUE_ID_SET = 'CONFERENCE_UNIQUE_ID_SET';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of (redux) action, which indicates conference subject changes.
|
* The type of (redux) action, which indicates conference subject changes.
|
||||||
*
|
*
|
||||||
|
@ -72,6 +62,16 @@ export const CONFERENCE_SUBJECT_CHANGED = 'CONFERENCE_SUBJECT_CHANGED';
|
||||||
*/
|
*/
|
||||||
export const CONFERENCE_TIMESTAMP_CHANGED = 'CONFERENCE_TIMESTAMP_CHANGED';
|
export const CONFERENCE_TIMESTAMP_CHANGED = 'CONFERENCE_TIMESTAMP_CHANGED';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of (redux) action which signals that an uuid for a conference has been set.
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* type: CONFERENCE_UNIQUE_ID_SET,
|
||||||
|
* conference: JitsiConference
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export const CONFERENCE_UNIQUE_ID_SET = 'CONFERENCE_UNIQUE_ID_SET';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of (redux) action which signals that a specific conference will be
|
* The type of (redux) action which signals that a specific conference will be
|
||||||
* joined.
|
* joined.
|
||||||
|
|
|
@ -296,22 +296,6 @@ export function conferenceLeft(conference: Object) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals that the unique identifier for conference has been set.
|
|
||||||
*
|
|
||||||
* @param {JitsiConference} conference - The JitsiConference instance, where the uuid has been set.
|
|
||||||
* @returns {{
|
|
||||||
* type: CONFERENCE_UNIQUE_ID_SET,
|
|
||||||
* conference: JitsiConference,
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
export function conferenceUniqueIdSet(conference: Object) {
|
|
||||||
return {
|
|
||||||
type: CONFERENCE_UNIQUE_ID_SET,
|
|
||||||
conference
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signals that the conference subject has been changed.
|
* Signals that the conference subject has been changed.
|
||||||
*
|
*
|
||||||
|
@ -344,6 +328,22 @@ export function conferenceTimestampChanged(conferenceTimestamp: number) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signals that the unique identifier for conference has been set.
|
||||||
|
*
|
||||||
|
* @param {JitsiConference} conference - The JitsiConference instance, where the uuid has been set.
|
||||||
|
* @returns {{
|
||||||
|
* type: CONFERENCE_UNIQUE_ID_SET,
|
||||||
|
* conference: JitsiConference,
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
export function conferenceUniqueIdSet(conference: Object) {
|
||||||
|
return {
|
||||||
|
type: CONFERENCE_UNIQUE_ID_SET,
|
||||||
|
conference
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds any existing local tracks to a specific conference before the conference
|
* Adds any existing local tracks to a specific conference before the conference
|
||||||
* is joined. Then signals the intention of the application to have the local
|
* is joined. Then signals the intention of the application to have the local
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
createPinnedEvent,
|
createPinnedEvent,
|
||||||
sendAnalytics
|
sendAnalytics
|
||||||
} from '../../analytics';
|
} from '../../analytics';
|
||||||
|
import { reloadNow } from '../../app/actions';
|
||||||
import { openDisplayNamePrompt } from '../../display-name';
|
import { openDisplayNamePrompt } from '../../display-name';
|
||||||
import { showErrorNotification } from '../../notifications';
|
import { showErrorNotification } from '../../notifications';
|
||||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
||||||
|
@ -117,6 +118,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||||
function _conferenceFailed({ dispatch, getState }, next, action) {
|
function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||||
const result = next(action);
|
const result = next(action);
|
||||||
const { conference, error } = action;
|
const { conference, error } = action;
|
||||||
|
const { enableForcedReload } = getState()['features/base/config'];
|
||||||
|
|
||||||
// Handle specific failure reasons.
|
// Handle specific failure reasons.
|
||||||
switch (error.name) {
|
switch (error.name) {
|
||||||
|
@ -130,6 +132,16 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
|
||||||
|
if (enableForcedReload) {
|
||||||
|
dispatch(showErrorNotification({
|
||||||
|
description: 'Restart initiated because of a bridge failure',
|
||||||
|
titleKey: 'dialog.sessionRestarted'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case JitsiConferenceErrors.CONNECTION_ERROR: {
|
case JitsiConferenceErrors.CONNECTION_ERROR: {
|
||||||
const [ msg ] = error.params;
|
const [ msg ] = error.params;
|
||||||
|
|
||||||
|
@ -147,26 +159,26 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
if (typeof APP === 'undefined') {
|
||||||
// conference is handled by /conference.js and appropriate failure handlers
|
!error.recoverable
|
||||||
// are set there.
|
|
||||||
if (typeof APP !== 'undefined') {
|
|
||||||
if (typeof beforeUnloadHandler !== 'undefined') {
|
|
||||||
window.removeEventListener('beforeunload', beforeUnloadHandler);
|
|
||||||
beforeUnloadHandler = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX After next(action), it is clear whether the error is recoverable.
|
|
||||||
!error.recoverable
|
|
||||||
&& conference
|
&& conference
|
||||||
&& conference.leave().catch(reason => {
|
&& conference.leave().catch(reason => {
|
||||||
// Even though we don't care too much about the failure, it may be
|
// Even though we don't care too much about the failure, it may be
|
||||||
// good to know that it happen, so log it (on the info level).
|
// good to know that it happen, so log it (on the info level).
|
||||||
logger.info('JitsiConference.leave() rejected with:', reason);
|
logger.info('JitsiConference.leave() rejected with:', reason);
|
||||||
});
|
});
|
||||||
|
} else if (typeof beforeUnloadHandler !== 'undefined') {
|
||||||
|
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||||
|
// conference is handled by /conference.js and appropriate failure handlers
|
||||||
|
// are set there.
|
||||||
|
window.removeEventListener('beforeunload', beforeUnloadHandler);
|
||||||
|
beforeUnloadHandler = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableForcedReload && error?.name === JitsiConferenceErrors.CONFERENCE_RESTARTED) {
|
||||||
|
dispatch(conferenceWillLeave(conference));
|
||||||
|
dispatch(reloadNow());
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,35 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
import { setPrejoinPageVisibility, setSkipPrejoinOnReload } from '../../prejoin';
|
||||||
|
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
||||||
|
|
||||||
|
import { CONFERENCE_FAILED, CONFERENCE_JOINED } from './actionTypes';
|
||||||
import './middleware.any';
|
import './middleware.any';
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
|
||||||
MiddlewareRegistry.register((/* store */) => next => action => {
|
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||||
|
const { enableForcedReload } = getState()['features/base/config'];
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case CONFERENCE_JOINED: {
|
||||||
|
if (enableForcedReload) {
|
||||||
|
dispatch(setPrejoinPageVisibility(false));
|
||||||
|
dispatch(setSkipPrejoinOnReload(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONFERENCE_FAILED: {
|
||||||
|
enableForcedReload
|
||||||
|
&& action.error?.name === JitsiConferenceErrors.CONFERENCE_RESTARTED
|
||||||
|
&& dispatch(setSkipPrejoinOnReload(true));
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case TOGGLE_SCREENSHARING: {
|
case TOGGLE_SCREENSHARING: {
|
||||||
if (typeof APP === 'object') {
|
if (typeof APP === 'object') {
|
||||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
||||||
|
|
|
@ -19,6 +19,11 @@ export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
|
||||||
*/
|
*/
|
||||||
export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
|
export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action type to set the visiblity of the prejoin page when client is forcefully reloaded.
|
||||||
|
*/
|
||||||
|
export const SET_SKIP_PREJOIN_RELOAD = 'SET_SKIP_PREJOIN_RELOAD';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action type used to set the mandatory stance of the prejoin display name.
|
* Action type used to set the mandatory stance of the prejoin display name.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
SET_DIALOUT_STATUS,
|
SET_DIALOUT_STATUS,
|
||||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
||||||
SET_SKIP_PREJOIN,
|
SET_SKIP_PREJOIN,
|
||||||
|
SET_SKIP_PREJOIN_RELOAD,
|
||||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||||
SET_PRECALL_TEST_RESULTS,
|
SET_PRECALL_TEST_RESULTS,
|
||||||
SET_PREJOIN_DEVICE_ERRORS,
|
SET_PREJOIN_DEVICE_ERRORS,
|
||||||
|
@ -418,6 +419,20 @@ export function setSkipPrejoin(value: boolean) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the visibility of the prejoin page when a client reload
|
||||||
|
* is triggered as a result of call migration initiated by Jicofo.
|
||||||
|
*
|
||||||
|
* @param {boolean} value - The visibility value.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function setSkipPrejoinOnReload(value: boolean) {
|
||||||
|
return {
|
||||||
|
type: SET_SKIP_PREJOIN_RELOAD,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action used to set the visiblitiy of the 'JoinByPhoneDialog'.
|
* Action used to set the visiblitiy of the 'JoinByPhoneDialog'.
|
||||||
*
|
*
|
||||||
|
|
|
@ -149,7 +149,8 @@ export function isJoinByPhoneDialogVisible(state: Object): boolean {
|
||||||
export function isPrejoinPageEnabled(state: Object): boolean {
|
export function isPrejoinPageEnabled(state: Object): boolean {
|
||||||
return navigator.product !== 'ReactNative'
|
return navigator.product !== 'ReactNative'
|
||||||
&& state['features/base/config'].prejoinPageEnabled
|
&& state['features/base/config'].prejoinPageEnabled
|
||||||
&& !state['features/base/settings'].userSelectedSkipPrejoin;
|
&& !state['features/base/settings'].userSelectedSkipPrejoin
|
||||||
|
&& !(state['features/base/config'].enableForcedReload && state['features/prejoin'].skipPrejoinOnReload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReducerRegistry } from '../base/redux';
|
import { PersistenceRegistry, ReducerRegistry } from '../base/redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SET_DEVICE_STATUS,
|
SET_DEVICE_STATUS,
|
||||||
|
@ -10,7 +10,8 @@ import {
|
||||||
SET_PREJOIN_DEVICE_ERRORS,
|
SET_PREJOIN_DEVICE_ERRORS,
|
||||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
||||||
SET_PREJOIN_PAGE_VISIBILITY,
|
SET_PREJOIN_PAGE_VISIBILITY,
|
||||||
SET_SKIP_PREJOIN
|
SET_SKIP_PREJOIN,
|
||||||
|
SET_SKIP_PREJOIN_RELOAD
|
||||||
} from './actionTypes';
|
} from './actionTypes';
|
||||||
|
|
||||||
const DEFAULT_STATE = {
|
const DEFAULT_STATE = {
|
||||||
|
@ -28,10 +29,24 @@ const DEFAULT_STATE = {
|
||||||
name: '',
|
name: '',
|
||||||
rawError: '',
|
rawError: '',
|
||||||
showPrejoin: true,
|
showPrejoin: true,
|
||||||
|
skipPrejoinOnReload: false,
|
||||||
showJoinByPhoneDialog: false,
|
showJoinByPhoneDialog: false,
|
||||||
userSelectedSkipPrejoin: false
|
userSelectedSkipPrejoin: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the redux store/state property which is the root of the redux
|
||||||
|
* state of the feature {@code prejoin}.
|
||||||
|
*/
|
||||||
|
const STORE_NAME = 'features/prejoin';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the persistence of the feature {@code prejoin}.
|
||||||
|
*/
|
||||||
|
PersistenceRegistry.register(STORE_NAME, {
|
||||||
|
skipPrejoinOnReload: true
|
||||||
|
}, DEFAULT_STATE);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for actions that mutate the prejoin state
|
* Listen for actions that mutate the prejoin state
|
||||||
*/
|
*/
|
||||||
|
@ -46,6 +61,13 @@ ReducerRegistry.register(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case SET_SKIP_PREJOIN_RELOAD: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
skipPrejoinOnReload: action.value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case SET_PRECALL_TEST_RESULTS:
|
case SET_PRECALL_TEST_RESULTS:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
Loading…
Reference in New Issue