diff --git a/react/features/authentication/middleware.ts b/react/features/authentication/middleware.ts index b4ca7f310609..ccbec0c35317 100644 --- a/react/features/authentication/middleware.ts +++ b/react/features/authentication/middleware.ts @@ -157,24 +157,6 @@ MiddlewareRegistry.register(store => next => action => { store.dispatch(hideLoginDialog()); break; - case CONNECTION_FAILED: { - const { error } = action; - const { getState } = store; - const state = getState(); - const { jwt } = state['features/base/jwt']; - - if (error - && error.name === JitsiConnectionErrors.PASSWORD_REQUIRED - && typeof error.recoverable === 'undefined' - && !jwt) { - error.recoverable = true; - - _handleLogin(store); - } - - break; - } - case LOGIN: { _handleLogin(store); diff --git a/react/features/base/conference/middleware.any.ts b/react/features/base/conference/middleware.any.ts index b9df6a6a16ce..d2d0db80c9ed 100644 --- a/react/features/base/conference/middleware.any.ts +++ b/react/features/base/conference/middleware.any.ts @@ -443,84 +443,33 @@ function _logJwtErrors(message: string, errors: string) { * @returns {Object} The value returned by {@code next(action)}. */ function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) { - const { connection, error } = action; - const { jwt } = getState()['features/base/jwt']; - - if (jwt) { - const errors: string = validateJwt(jwt).map((err: any) => - i18n.t(`dialog.tokenAuthFailedReason.${err.key}`, err.args)) - .join(' '); - - _logJwtErrors(error.message, errors); - - // do not show the notification when we will prompt the user - // for username and password - if (error.name === JitsiConnectionErrors.PASSWORD_REQUIRED) { - dispatch(showErrorNotification({ - descriptionKey: errors ? 'dialog.tokenAuthFailedWithReasons' : 'dialog.tokenAuthFailed', - descriptionArguments: { reason: errors }, - titleKey: 'dialog.tokenAuthFailedTitle' - })); - } - } - - if (error.name === JitsiConnectionErrors.CONFERENCE_REQUEST_FAILED) { - let notificationAction: Function = showNotification; - const notificationProps = { - customActionNameKey: [ 'dialog.rejoinNow' ], - customActionHandler: [ () => dispatch(reloadNow()) ], - descriptionKey: 'notify.connectionFailed' - } as INotificationProps; - - const { locationURL = { href: '' } as URL } = getState()['features/base/connection']; - const { tenant = '' } = parseURIString(locationURL.href) || {}; - - if (tenant.startsWith('-') || tenant.endsWith('-')) { - notificationProps.descriptionKey = 'notify.invalidTenantHyphenDescription'; - notificationProps.titleKey = 'notify.invalidTenant'; - notificationAction = showErrorNotification; - } else if (tenant.length > 63) { - notificationProps.descriptionKey = 'notify.invalidTenantLengthDescription'; - notificationProps.titleKey = 'notify.invalidTenant'; - notificationAction = showErrorNotification; - } - - dispatch(notificationAction(notificationProps, NOTIFICATION_TIMEOUT_TYPE.STICKY)); - } - - const result = next(action); - - _removeUnloadHandler(getState); - - forEachConference(getState, conference => { - // TODO: revisit this - // It feels that it would make things easier if JitsiConference - // in lib-jitsi-meet would monitor it's connection and emit - // CONFERENCE_FAILED when it's dropped. It has more knowledge on - // whether it can recover or not. But because the reload screen - // and the retry logic is implemented in the app maybe it can be - // left this way for now. - if (conference.getConnection() === connection) { - // XXX Note that on mobile the error type passed to - // connectionFailed is always an object with .name property. - // This fact needs to be checked prior to enabling this logic on - // web. - const conferenceAction = conferenceFailed(conference, error.name); - - // Copy the recoverable flag if set on the CONNECTION_FAILED - // action to not emit recoverable action caused by - // a non-recoverable one. - if (typeof error.recoverable !== 'undefined') { - conferenceAction.error.recoverable = error.recoverable; + + console.log("[AUTO_RECONNECT] entered _connectionFailed middleware"); + const { connection } = action; + const state = getState(); + const conference = state['features/base/conference'].conference; + (async () => { + try { + if (conference) { + console.log("[AUTO_RECONNECT] Leaving conference"); + dispatch(conferenceWillLeave(conference)); + await conference.leave(); + } + + if (connection) { + console.log("[AUTO_RECONNECT] Disconnecting connection"); + await connection.disconnect(); } - dispatch(conferenceAction); + next(action); + console.log("[AUTO_RECONNECT] Start new connection"); + return dispatch(connect()); + + } catch (err) { + console.error("[AUTO_RECONNECT] _connectionFailed failed:", err); + } - - return true; - }); - - return result; + })(); } /** diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.connection.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.connection.ts index 7bff10440763..16110c011ce5 100644 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.connection.ts +++ b/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.connection.ts @@ -1,6 +1,5 @@ import { IStore } from '../../../../../app/types'; import { isLeavingConferenceManually } from "../../../general/utils/conferenceState"; -import { isAutoReconnecting } from "../middleware.auto-reconnect"; import { showConnectionFailedNotification, showConnectionLostNotification } from "./notification-helpers"; /** @@ -22,7 +21,6 @@ export const handleXMPPDisconnected = (dispatch: IStore["dispatch"], message: st console.log("[CONNECTION_NOTIFICATIONS] XMPP disconnected:", message); if (isLeavingConferenceManually()) return; - if (isAutoReconnecting()) return; showConnectionLostNotification(dispatch); }; @@ -37,7 +35,6 @@ export const handleXMPPDisconnected = (dispatch: IStore["dispatch"], message: st */ export const handleXMPPConnectionFailed = (dispatch: IStore["dispatch"], error: any, message: string) => { console.error("[CONNECTION_NOTIFICATIONS] XMPP connection failed:", error, message); - if (isAutoReconnecting()) return; showConnectionFailedNotification(dispatch, message); }; diff --git a/react/features/base/meet/middlewares/connection-stability/index.ts b/react/features/base/meet/middlewares/connection-stability/index.ts index bacad5c7140c..c260460526a7 100644 --- a/react/features/base/meet/middlewares/connection-stability/index.ts +++ b/react/features/base/meet/middlewares/connection-stability/index.ts @@ -13,7 +13,6 @@ import './connection-notifications'; import './middleware.datachannel'; import './middleware.error-handling'; import './middleware.poor-connection'; -import './middleware.auto-reconnect'; export { }; diff --git a/react/features/base/meet/middlewares/connection-stability/middleware.auto-reconnect.ts b/react/features/base/meet/middlewares/connection-stability/middleware.auto-reconnect.ts deleted file mode 100644 index 8f4bd01c70bf..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/middleware.auto-reconnect.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { batch } from "react-redux"; -import { AnyAction } from "redux"; -import { IStore } from "../../../../app/types"; -import { hideNotification } from "../../../../notifications/actions"; -import { CONFERENCE_WILL_LEAVE } from "../../../conference/actionTypes"; -import { isLeavingConferenceManually, setLeaveConferenceManually } from "../../general/utils/conferenceState"; -import { CONNECTION_DISCONNECTED, CONNECTION_ESTABLISHED, CONNECTION_FAILED } from "../../../connection/actionTypes"; -import { connect } from "../../../connection/actions.web"; -import { setJWT } from "../../../jwt/actions"; -import MiddlewareRegistry from "../../../redux/MiddlewareRegistry"; -import { trackRemoved } from "../../../tracks/actions.any"; -import { hideLoader, showLoader } from "../../loader"; - -const RECONNECTION_NOTIFICATION_ID = "connection.reconnecting"; -const RECONNECTION_LOADER_ID = "auto-reconnect"; -const RECONNECTION_WAIT_TIME_MS = 15000; -const MAX_RECONNECTION_ATTEMPTS = 2; -const RECONNECTION_DELAY_MS = 3000; -const JWT_EXPIRED_ERROR = "connection.passwordRequired"; - -let reconnectionTimer: number | null = null; -let isReconnecting = false; -let reconnectionAttempts = 0; - -export const isAutoReconnecting = () => isReconnecting; - -const hideReconnectionNotification = (store: IStore) => { - store.dispatch(hideNotification(RECONNECTION_NOTIFICATION_ID)); -}; - -const showReconnectionLoader = (store: IStore, attempt: number) => { - const textKey = attempt <= MAX_RECONNECTION_ATTEMPTS ? "loader.reconnecting" : "loader.reloading"; - - store.dispatch(showLoader(undefined, textKey, RECONNECTION_LOADER_ID)); -}; - -const hideReconnectionLoader = (store: IStore) => { - store.dispatch(hideLoader(RECONNECTION_LOADER_ID)); -}; - -const reloadPage = () => { - window.location.reload(); -}; - -const clearExpiredJWT = (store: IStore) => { - store.dispatch(setJWT(undefined)); -}; - -const clearRemoteTracks = (store: IStore) => { - const state = store.getState(); - const remoteTracks = state["features/base/tracks"].filter((t) => !t.local); - - batch(() => { - for (const track of remoteTracks) { - store.dispatch(trackRemoved(track.jitsiTrack)); - } - }); -}; - -const triggerReconnection = (store: IStore) => { - store.dispatch(connect()); -}; - -const scheduleRetry = (store: IStore) => { - reconnectionTimer = window.setTimeout(() => { - if (!isLeavingConferenceManually() && isReconnecting) { - attemptReconnection(store); - } - }, RECONNECTION_DELAY_MS); -}; - -const handleMaxAttemptsReached = (store: IStore) => { - isReconnecting = true; - showReconnectionLoader(store, reconnectionAttempts + 1); - reconnectionTimer = window.setTimeout(reloadPage, 2000); -}; - -/** - * Attempts to reconnect by clearing JWT and connecting to conference again. - * If max attempts reached, reloads the page. - */ -const attemptReconnection = async (store: IStore) => { - if (isLeavingConferenceManually()) return; - - if (reconnectionAttempts >= MAX_RECONNECTION_ATTEMPTS) { - handleMaxAttemptsReached(store); - return; - } - - reconnectionAttempts++; - isReconnecting = true; - showReconnectionLoader(store, reconnectionAttempts); - - try { - clearRemoteTracks(store); - clearExpiredJWT(store); - await new Promise((resolve) => setTimeout(resolve, 100)); - triggerReconnection(store); - scheduleRetry(store); - } catch (error) { - console.error("[AUTO_RECONNECT] Reconnection error:", error); - scheduleRetry(store); - } -}; - -const clearTimer = () => { - if (reconnectionTimer !== null) { - clearTimeout(reconnectionTimer); - reconnectionTimer = null; - } -}; - -const resetReconnectionState = () => { - clearTimer(); - reconnectionAttempts = 0; - isReconnecting = false; -}; - -/** - * Middleware that handles automatic reconnection when JWT expires or connection is lost. - */ -MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyAction) => { - const result = next(action); - - switch (action.type) { - case CONFERENCE_WILL_LEAVE: { - setLeaveConferenceManually(true); - resetReconnectionState(); - hideReconnectionNotification(store); - hideReconnectionLoader(store); - break; - } - - case CONNECTION_DISCONNECTED: { - if (isLeavingConferenceManually()) break; - - clearTimer(); - reconnectionAttempts = 0; - isReconnecting = true; - - reconnectionTimer = window.setTimeout(() => { - if (!isLeavingConferenceManually() && isReconnecting) { - attemptReconnection(store); - } - }, RECONNECTION_WAIT_TIME_MS); - - break; - } - - case CONNECTION_ESTABLISHED: { - if (isReconnecting) { - hideReconnectionNotification(store); - hideReconnectionLoader(store); - } - - resetReconnectionState(); - setLeaveConferenceManually(false); - break; - } - - case CONNECTION_FAILED: { - const { error } = action; - console.log("[AUTO_RECONNECT] Connection failed with error:", error); - if (error?.name === JWT_EXPIRED_ERROR && !isLeavingConferenceManually() && !isReconnecting) { - attemptReconnection(store); - } - - break; - } - } - - return result; -}); - -export default {}; diff --git a/react/features/base/meet/views/Conference/components/VideoParticipant.tsx b/react/features/base/meet/views/Conference/components/VideoParticipant.tsx index 207c79325663..f66794c611ca 100644 --- a/react/features/base/meet/views/Conference/components/VideoParticipant.tsx +++ b/react/features/base/meet/views/Conference/components/VideoParticipant.tsx @@ -49,7 +49,7 @@ const VideoParticipant = ({ )} key={`video-${id}`} // Set to false due to decoding issues and video lag - encodeVideo={true} + encodeVideo={false} /> ) : (