From 197e79e061508491f9de5f2fdd2adf923b1a9144 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 11:18:58 +0100 Subject: [PATCH 01/17] disable auto reconnect --- .../connection-notifications/event-handlers.connection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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..8fee787d1a08 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,8 +1,9 @@ import { IStore } from '../../../../../app/types'; import { isLeavingConferenceManually } from "../../../general/utils/conferenceState"; -import { isAutoReconnecting } from "../middleware.auto-reconnect"; +//import { isAutoReconnecting } from "../middleware.auto-reconnect"; import { showConnectionFailedNotification, showConnectionLostNotification } from "./notification-helpers"; +const isAutoReconnecting = () => false; /** * Handles when XMPP connection is established * This is the signaling connection, not media From 56a2405adf3036190cf2be91d4b2a967a9f06d5c Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 11:22:25 +0100 Subject: [PATCH 02/17] up the lib version --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b629cbdea321..ce7bf80a03ef 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "js-md5": "0.6.1", "js-sha512": "0.8.0", "jwt-decode": "2.2.0", - "lib-jitsi-meet": "https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20/lib-jitsi-meet-0.0.20.tgz", + "lib-jitsi-meet": "https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20-debug/lib-jitsi-meet-0.0.20-debug.tgz", "lodash-es": "4.17.23", "moment": "2.29.4", "moment-duration-format": "2.2.2", diff --git a/yarn.lock b/yarn.lock index 3a191af5bdac..3b282a6fcde0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11081,9 +11081,9 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -"lib-jitsi-meet@https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20/lib-jitsi-meet-0.0.20.tgz": - version "0.0.20" - resolved "https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20/lib-jitsi-meet-0.0.20.tgz#5048eba36fa1f6b1884c00d6d5a21e847c9d2c46" +"lib-jitsi-meet@https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20-debug/lib-jitsi-meet-0.0.20-debug.tgz": + version "0.0.20-debug" + resolved "https://github.com/internxt/lib-jitsi-meet/releases/download/v.0.0.20-debug/lib-jitsi-meet-0.0.20-debug.tgz#3e5a6b196a215d2dc449c588c23ad32bded2b1de" dependencies: "@hexagon/base64" "^2.0.4" "@jitsi/js-utils" "^2.6.7" From 5893e9a2e4f01ac854758f9c32f7014924ab732f Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 11:36:00 +0100 Subject: [PATCH 03/17] enable auto-reconnect --- .../connection-notifications/event-handlers.connection.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 8fee787d1a08..7bff10440763 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,9 +1,8 @@ import { IStore } from '../../../../../app/types'; import { isLeavingConferenceManually } from "../../../general/utils/conferenceState"; -//import { isAutoReconnecting } from "../middleware.auto-reconnect"; +import { isAutoReconnecting } from "../middleware.auto-reconnect"; import { showConnectionFailedNotification, showConnectionLostNotification } from "./notification-helpers"; -const isAutoReconnecting = () => false; /** * Handles when XMPP connection is established * This is the signaling connection, not media From 7370e497284ac7b52d5df4a4cbf44dab34ba2dde Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 12:49:47 +0100 Subject: [PATCH 04/17] do the same as jitsi --- react/features/base/connection/actions.web.ts | 134 +++--------------- .../event-handlers.connection.ts | 3 - 2 files changed, 19 insertions(+), 118 deletions(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index a69fb11f29b3..44c584076e69 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -1,126 +1,30 @@ -// @ts-expect-error -import { jitsiLocalStorage } from '@jitsi/js-utils'; +import MiddlewareRegistry from '../redux/MiddlewareRegistry'; -import { IStore } from '../../app/types'; -import { getCustomerDetails } from '../../jaas/actions.any'; -import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions'; -import { showWarningNotification } from '../../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants'; -import { stopLocalVideoRecording } from '../../recording/actions.any'; -import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web'; -import { setJWT } from '../jwt/actions'; - -import { LocalStorageManager } from "../meet/LocalStorageManager"; -import MeetingService from "../meet/services/meeting.service"; -import { _connectInternal } from "./actions.any"; -import logger from './logger'; - -export * from "./actions.any"; +import { CONNECTION_WILL_CONNECT } from './actionTypes'; /** - * Opens new connection. + * The feature announced so we can distinguish jibri participants. * - * @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}). - * @param {string} [password] - The XMPP user's password. - * @returns {Function} + * @type {string} */ -export function connect(id?: string, password?: string) { - return (dispatch: IStore["dispatch"], getState: IStore["getState"]) => { - const state = getState(); - const { jwt } = state["features/base/jwt"]; - const { iAmRecorder, iAmSipGateway } = state["features/base/config"]; - // TODO: CHECK WHY USER REDUCER IS NULL IN THIS POINT, initializers are not executing as expected - // const { user } = state["features/user"]; - const user = LocalStorageManager.instance.getUser(); - if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) { - return dispatch(getCustomerDetails()) - .then(() => { - if (!jwt) { - return getJaasJWT(state); - } - }) - .then((j) => j && dispatch(setJWT(j))) - .then(() => - dispatch( - _connectInternal({ - id, - password, - name: user?.name, - lastname: user?.lastname, - isAnonymous: !user, - }) - ) - ) - // latest jitsi changes, test if not works current ones - // .then(j => { - // j && dispatch(setJWT(j)); - - // return dispatch(_connectInternal(id, password)); - // }) - .catch(e => { - logger.error('Connection error', e); - }); - } +export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri'; - // used by jibri - const usernameOverride = jitsiLocalStorage.getItem("xmpp_username_override"); - const passwordOverride = jitsiLocalStorage.getItem("xmpp_password_override"); +MiddlewareRegistry.register(({ getState }) => next => action => { + switch (action.type) { + case CONNECTION_WILL_CONNECT: { + const { connection } = action; + const { iAmRecorder } = getState()['features/base/config']; - if (usernameOverride && usernameOverride.length > 0) { - id = usernameOverride; // eslint-disable-line no-param-reassign - } - if (passwordOverride && passwordOverride.length > 0) { - password = passwordOverride; // eslint-disable-line no-param-reassign + if (iAmRecorder) { + connection.addFeature(DISCO_JIBRI_FEATURE); } - return dispatch( - _connectInternal({ - id, - password, - name: user?.name, - lastname: user?.lastname, - isAnonymous: !user, - }) - ); - }; -} - -/** - * Closes connection. - * - * @param {boolean} [requestFeedback] - Whether to attempt showing a - * request for call feedback. - * @param {string} [feedbackTitle] - The feedback title. - * @param {boolean} [notifyOnConferenceTermination] - Whether to notify - * the user on conference termination. - * @returns {Function} - */ -export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) { - // XXX For web based version we use conference hanging up logic from the old app. - return async (dispatch: IStore["dispatch"]) => { - if (LocalRecordingManager.isRecordingLocally()) { - dispatch(stopLocalVideoRecording()); - dispatch( - showWarningNotification( - { - titleKey: "localRecording.stopping", - descriptionKey: "localRecording.wait", - }, - NOTIFICATION_TIMEOUT_TYPE.STICKY - ) - ); + // @ts-ignore + APP.connection = connection; - // wait 1000ms for the recording to end and start downloading - await new Promise((res) => { - setTimeout(res, 1000); - }); - } - - if (!roomId) { - return Promise.reject(new Error("No roomId provided to hangup")); - } - MeetingService.instance.leaveCall(roomId); + break; + } + } - return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination); - }; -} + return next(action); +}); \ No newline at end of file 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); }; From 6ca983ce2544c265ac56e145702d1b21305504dd Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 14:11:02 +0100 Subject: [PATCH 05/17] use jitsi code --- react/features/base/connection/actions.web.ts | 98 +++++++++++++++---- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index 44c584076e69..1d2a8bbec067 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -1,30 +1,90 @@ -import MiddlewareRegistry from '../redux/MiddlewareRegistry'; +// @ts-expect-error +import { jitsiLocalStorage } from '@jitsi/js-utils'; -import { CONNECTION_WILL_CONNECT } from './actionTypes'; +import { IStore } from '../../app/types'; +import { getCustomerDetails } from '../../jaas/actions.any'; +import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions'; +import { showWarningNotification } from '../../notifications/actions'; +import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants'; +import { stopLocalVideoRecording } from '../../recording/actions.any'; +import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web'; +import { setJWT } from '../jwt/actions'; + +import { _connectInternal } from './actions.any'; +import logger from './logger'; + +export * from './actions.any'; /** - * The feature announced so we can distinguish jibri participants. + * Opens new connection. * - * @type {string} + * @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}). + * @param {string} [password] - The XMPP user's password. + * @returns {Function} */ -export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri'; +export function connect(id?: string, password?: string) { + return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { + const state = getState(); + const { jwt } = state['features/base/jwt']; + const { iAmRecorder, iAmSipGateway } = state['features/base/config']; + + if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) { + return dispatch(getCustomerDetails()) + .then(() => { + if (!jwt) { + return getJaasJWT(state); + } + }) + .then(j => { + j && dispatch(setJWT(j)); -MiddlewareRegistry.register(({ getState }) => next => action => { - switch (action.type) { - case CONNECTION_WILL_CONNECT: { - const { connection } = action; - const { iAmRecorder } = getState()['features/base/config']; + return dispatch(_connectInternal(id, password)); + }).catch(e => { + logger.error('Connection error', e); + }); + } + + // used by jibri + const usernameOverride = jitsiLocalStorage.getItem('xmpp_username_override'); + const passwordOverride = jitsiLocalStorage.getItem('xmpp_password_override'); - if (iAmRecorder) { - connection.addFeature(DISCO_JIBRI_FEATURE); + if (usernameOverride && usernameOverride.length > 0) { + id = usernameOverride; // eslint-disable-line no-param-reassign + } + if (passwordOverride && passwordOverride.length > 0) { + password = passwordOverride; // eslint-disable-line no-param-reassign } - // @ts-ignore - APP.connection = connection; + return dispatch(_connectInternal(id, password)); + }; +} - break; - } - } +/** + * Closes connection. + * + * @param {boolean} [requestFeedback] - Whether to attempt showing a + * request for call feedback. + * @param {string} [feedbackTitle] - The feedback title. + * @param {boolean} [notifyOnConferenceTermination] - Whether to notify + * the user on conference termination. + * @returns {Function} + */ +export function hangup(requestFeedback = false, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) { + // XXX For web based version we use conference hanging up logic from the old app. + return async (dispatch: IStore['dispatch']) => { + if (LocalRecordingManager.isRecordingLocally()) { + dispatch(stopLocalVideoRecording()); + dispatch(showWarningNotification({ + titleKey: 'localRecording.stopping', + descriptionKey: 'localRecording.wait' + }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); + + // wait 1000ms for the recording to end and start downloading + await new Promise(res => { + setTimeout(res, 1000); + }); + } - return next(action); -}); \ No newline at end of file + return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination); + }; +} \ No newline at end of file From 97def0f6c784ac05a23f1b96d3c1e0c902e37d7c Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 14:30:25 +0100 Subject: [PATCH 06/17] middleware from jitsi --- .../base/connection/middleware.web.ts | 51 +++++-------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/react/features/base/connection/middleware.web.ts b/react/features/base/connection/middleware.web.ts index a7189a5e0270..44c584076e69 100644 --- a/react/features/base/connection/middleware.web.ts +++ b/react/features/base/connection/middleware.web.ts @@ -1,53 +1,30 @@ -import { redirectToStaticPage } from '../../app/actions.any'; -import { CONFERENCE_WILL_LEAVE } from "../conference/actionTypes"; -import { isLeavingConferenceManually, setLeaveConferenceManually } from "../meet/general/utils/conferenceState"; -import MiddlewareRegistry from "../redux/MiddlewareRegistry"; +import MiddlewareRegistry from '../redux/MiddlewareRegistry'; -import { CONNECTION_DISCONNECTED, CONNECTION_WILL_CONNECT } from "./actionTypes"; +import { CONNECTION_WILL_CONNECT } from './actionTypes'; /** * The feature announced so we can distinguish jibri participants. * * @type {string} */ -export const DISCO_JIBRI_FEATURE = "http://jitsi.org/protocol/jibri"; +export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri'; -MiddlewareRegistry.register(({ getState, dispatch }) => (next) => (action) => { +MiddlewareRegistry.register(({ getState }) => next => action => { switch (action.type) { - case CONNECTION_WILL_CONNECT: { - const { connection } = action; - const { iAmRecorder } = getState()["features/base/config"]; + case CONNECTION_WILL_CONNECT: { + const { connection } = action; + const { iAmRecorder } = getState()['features/base/config']; - if (iAmRecorder) { - connection.addFeature(DISCO_JIBRI_FEATURE); - } - - // @ts-ignore - APP.connection = connection; - - setLeaveConferenceManually(false); - break; - } - - case CONFERENCE_WILL_LEAVE: { - setLeaveConferenceManually(true); - break; + if (iAmRecorder) { + connection.addFeature(DISCO_JIBRI_FEATURE); } - case CONNECTION_DISCONNECTED: { - if (isLeavingConferenceManually()) { - setLeaveConferenceManually(false); + // @ts-ignore + APP.connection = connection; - setTimeout(() => { - dispatch(redirectToStaticPage("/")); - }, 2000); - } else { - console.warn("Connection disconnected unexpectedly - waiting for reconnection"); - } - - break; - } + break; + } } return next(action); -}); +}); \ No newline at end of file From 78e333225e36490688f00db009da6947f5e61585 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 14:42:11 +0100 Subject: [PATCH 07/17] fix input --- react/features/base/connection/actions.web.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index 1d2a8bbec067..00bf50df89bd 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -38,7 +38,7 @@ export function connect(id?: string, password?: string) { .then(j => { j && dispatch(setJWT(j)); - return dispatch(_connectInternal(id, password)); + return dispatch(_connectInternal({id, password})); }).catch(e => { logger.error('Connection error', e); }); From f6da4c36a2e09bf9f23e768a390f4d615fcf9ac7 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 14:54:50 +0100 Subject: [PATCH 08/17] fix type errors --- react/features/base/connection/actions.web.ts | 2 +- react/features/conference/middleware.web.ts | 23 +++++-------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index 00bf50df89bd..933b7c0affbe 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -55,7 +55,7 @@ export function connect(id?: string, password?: string) { password = passwordOverride; // eslint-disable-line no-param-reassign } - return dispatch(_connectInternal(id, password)); + return dispatch(_connectInternal({id, password})); }; } diff --git a/react/features/conference/middleware.web.ts b/react/features/conference/middleware.web.ts index 432ba6bf21e3..2d1e857f6b18 100644 --- a/react/features/conference/middleware.web.ts +++ b/react/features/conference/middleware.web.ts @@ -24,30 +24,19 @@ MiddlewareRegistry.register(store => next => action => { } case KICKED_OUT: { - const { dispatch, getState } = store; + const { dispatch } = store; const { participant } = action; - const { room } = getState()["features/base/conference"]; // we need to first finish dispatching or the notification can be cleared out const result = next(action); const participantDisplayName - = getParticipantDisplayName(store.getState, participant.getId()); - const roomId = room ?? ""; - - dispatch(hangup(true, roomId, i18next.t("dialog.kickTitle", { participantDisplayName }))); - // Jitsi latest version - // dispatch( - // hangup( - // true, - // roomId, - // participantDisplayName - // ? i18next.t("dialog.kickTitle", { participantDisplayName }) - // : i18next.t("dialog.kickSystemTitle"), - // true - // ) - // ); + = participant && getParticipantDisplayName(store.getState, participant.getId()); + dispatch(hangup(true, + participantDisplayName ? i18next.t('dialog.kickTitle', { participantDisplayName }) + : i18next.t('dialog.kickSystemTitle'), + true)); return result; } } From 1095b21816500f0205e2ea914a923702e7470756 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 15:53:17 +0100 Subject: [PATCH 09/17] revert style changes --- react/features/base/connection/actions.web.ts | 20 +++++++++---------- .../base/connection/middleware.web.ts | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index 933b7c0affbe..60734b695f88 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -10,10 +10,10 @@ import { stopLocalVideoRecording } from '../../recording/actions.any'; import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web'; import { setJWT } from '../jwt/actions'; -import { _connectInternal } from './actions.any'; +import { _connectInternal } from "./actions.any"; import logger from './logger'; -export * from './actions.any'; +export * from "./actions.any"; /** * Opens new connection. @@ -23,10 +23,10 @@ export * from './actions.any'; * @returns {Function} */ export function connect(id?: string, password?: string) { - return (dispatch: IStore['dispatch'], getState: IStore['getState']) => { + return (dispatch: IStore["dispatch"], getState: IStore["getState"]) => { const state = getState(); - const { jwt } = state['features/base/jwt']; - const { iAmRecorder, iAmSipGateway } = state['features/base/config']; + const { jwt } = state["features/base/jwt"]; + const { iAmRecorder, iAmSipGateway } = state["features/base/config"]; if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) { return dispatch(getCustomerDetails()) @@ -45,8 +45,8 @@ export function connect(id?: string, password?: string) { } // used by jibri - const usernameOverride = jitsiLocalStorage.getItem('xmpp_username_override'); - const passwordOverride = jitsiLocalStorage.getItem('xmpp_password_override'); + const usernameOverride = jitsiLocalStorage.getItem("xmpp_username_override"); + const passwordOverride = jitsiLocalStorage.getItem("xmpp_password_override"); if (usernameOverride && usernameOverride.length > 0) { id = usernameOverride; // eslint-disable-line no-param-reassign @@ -70,8 +70,8 @@ export function connect(id?: string, password?: string) { * @returns {Function} */ export function hangup(requestFeedback = false, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) { - // XXX For web based version we use conference hanging up logic from the old app. - return async (dispatch: IStore['dispatch']) => { + // XXX For web based version we use conference hanging up logic from the old app. + return async (dispatch: IStore["dispatch"]) => { if (LocalRecordingManager.isRecordingLocally()) { dispatch(stopLocalVideoRecording()); dispatch(showWarningNotification({ @@ -80,7 +80,7 @@ export function hangup(requestFeedback = false, feedbackTitle?: string, notifyOn }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); // wait 1000ms for the recording to end and start downloading - await new Promise(res => { + await new Promise((res) => { setTimeout(res, 1000); }); } diff --git a/react/features/base/connection/middleware.web.ts b/react/features/base/connection/middleware.web.ts index 44c584076e69..3596a32b060a 100644 --- a/react/features/base/connection/middleware.web.ts +++ b/react/features/base/connection/middleware.web.ts @@ -1,19 +1,19 @@ -import MiddlewareRegistry from '../redux/MiddlewareRegistry'; +import MiddlewareRegistry from "../redux/MiddlewareRegistry"; -import { CONNECTION_WILL_CONNECT } from './actionTypes'; +import { CONNECTION_WILL_CONNECT } from "./actionTypes"; /** * The feature announced so we can distinguish jibri participants. * * @type {string} */ -export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri'; +export const DISCO_JIBRI_FEATURE = "http://jitsi.org/protocol/jibri"; MiddlewareRegistry.register(({ getState }) => next => action => { switch (action.type) { case CONNECTION_WILL_CONNECT: { - const { connection } = action; - const { iAmRecorder } = getState()['features/base/config']; + const { connection } = action; + const { iAmRecorder } = getState()["features/base/config"]; if (iAmRecorder) { connection.addFeature(DISCO_JIBRI_FEATURE); From f3dbd36a835183b37675816f01b849e89f32d6e6 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 15:56:36 +0100 Subject: [PATCH 10/17] remove style changes --- .../base/connection/middleware.web.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/react/features/base/connection/middleware.web.ts b/react/features/base/connection/middleware.web.ts index 3596a32b060a..ae26056df8f0 100644 --- a/react/features/base/connection/middleware.web.ts +++ b/react/features/base/connection/middleware.web.ts @@ -9,22 +9,22 @@ import { CONNECTION_WILL_CONNECT } from "./actionTypes"; */ export const DISCO_JIBRI_FEATURE = "http://jitsi.org/protocol/jibri"; -MiddlewareRegistry.register(({ getState }) => next => action => { +MiddlewareRegistry.register(({ getState }) => (next) => (action) => { switch (action.type) { - case CONNECTION_WILL_CONNECT: { + case CONNECTION_WILL_CONNECT: { const { connection } = action; const { iAmRecorder } = getState()["features/base/config"]; - if (iAmRecorder) { - connection.addFeature(DISCO_JIBRI_FEATURE); - } + if (iAmRecorder) { + connection.addFeature(DISCO_JIBRI_FEATURE); + } - // @ts-ignore - APP.connection = connection; + // @ts-ignore + APP.connection = connection; - break; - } + break; + } } return next(action); -}); \ No newline at end of file +}); From a31740db6a34f941e450c6d008c782e981206d47 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 16:54:30 +0100 Subject: [PATCH 11/17] add logs for connection quality --- .../connection-stability/middleware.poor-connection.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts b/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts index 5f8f0f395638..04756692314f 100644 --- a/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts +++ b/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts @@ -65,6 +65,7 @@ const checkConnectionQuality = (store: IStore, connectionQuality: number) => { } if (connectionQuality < GOOD_CONNECTION_THRESHOLD) { + console.log('TEST: Poor connection detected, showing warning notification. Connection quality:', connectionQuality); showPoorConnectionWarning(store); } else { hidePoorConnectionWarning(store); @@ -77,6 +78,7 @@ const onStatsUpdated = (store: IStore) => (stats: IConnectionStats) => { } const connectionQuality = stats.connectionQuality; + console.log('TEST: Connection stats updated. Connection quality:', connectionQuality); if (typeof connectionQuality === 'number') { checkConnectionQuality(store, connectionQuality); From 0d84836f8e24c97e1810a74bf0f0f3c08194c0f1 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 17:16:26 +0100 Subject: [PATCH 12/17] revert changes for actions.web --- react/features/base/connection/actions.web.ts | 60 +++++++++++++++---- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index 60734b695f88..a69fb11f29b3 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -10,6 +10,8 @@ import { stopLocalVideoRecording } from '../../recording/actions.any'; import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web'; import { setJWT } from '../jwt/actions'; +import { LocalStorageManager } from "../meet/LocalStorageManager"; +import MeetingService from "../meet/services/meeting.service"; import { _connectInternal } from "./actions.any"; import logger from './logger'; @@ -27,7 +29,9 @@ export function connect(id?: string, password?: string) { const state = getState(); const { jwt } = state["features/base/jwt"]; const { iAmRecorder, iAmSipGateway } = state["features/base/config"]; - + // TODO: CHECK WHY USER REDUCER IS NULL IN THIS POINT, initializers are not executing as expected + // const { user } = state["features/user"]; + const user = LocalStorageManager.instance.getUser(); if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) { return dispatch(getCustomerDetails()) .then(() => { @@ -35,11 +39,25 @@ export function connect(id?: string, password?: string) { return getJaasJWT(state); } }) - .then(j => { - j && dispatch(setJWT(j)); + .then((j) => j && dispatch(setJWT(j))) + .then(() => + dispatch( + _connectInternal({ + id, + password, + name: user?.name, + lastname: user?.lastname, + isAnonymous: !user, + }) + ) + ) + // latest jitsi changes, test if not works current ones + // .then(j => { + // j && dispatch(setJWT(j)); - return dispatch(_connectInternal({id, password})); - }).catch(e => { + // return dispatch(_connectInternal(id, password)); + // }) + .catch(e => { logger.error('Connection error', e); }); } @@ -55,7 +73,15 @@ export function connect(id?: string, password?: string) { password = passwordOverride; // eslint-disable-line no-param-reassign } - return dispatch(_connectInternal({id, password})); + return dispatch( + _connectInternal({ + id, + password, + name: user?.name, + lastname: user?.lastname, + isAnonymous: !user, + }) + ); }; } @@ -69,15 +95,20 @@ export function connect(id?: string, password?: string) { * the user on conference termination. * @returns {Function} */ -export function hangup(requestFeedback = false, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) { +export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) { // XXX For web based version we use conference hanging up logic from the old app. return async (dispatch: IStore["dispatch"]) => { if (LocalRecordingManager.isRecordingLocally()) { dispatch(stopLocalVideoRecording()); - dispatch(showWarningNotification({ - titleKey: 'localRecording.stopping', - descriptionKey: 'localRecording.wait' - }, NOTIFICATION_TIMEOUT_TYPE.STICKY)); + dispatch( + showWarningNotification( + { + titleKey: "localRecording.stopping", + descriptionKey: "localRecording.wait", + }, + NOTIFICATION_TIMEOUT_TYPE.STICKY + ) + ); // wait 1000ms for the recording to end and start downloading await new Promise((res) => { @@ -85,6 +116,11 @@ export function hangup(requestFeedback = false, feedbackTitle?: string, notifyOn }); } + if (!roomId) { + return Promise.reject(new Error("No roomId provided to hangup")); + } + MeetingService.instance.leaveCall(roomId); + return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination); }; -} \ No newline at end of file +} From 900296daa0ecd5ab38510a82372e55b901d70e35 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 17:17:20 +0100 Subject: [PATCH 13/17] revert changes to middleware.web --- react/features/conference/middleware.web.ts | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/react/features/conference/middleware.web.ts b/react/features/conference/middleware.web.ts index 2d1e857f6b18..432ba6bf21e3 100644 --- a/react/features/conference/middleware.web.ts +++ b/react/features/conference/middleware.web.ts @@ -24,19 +24,30 @@ MiddlewareRegistry.register(store => next => action => { } case KICKED_OUT: { - const { dispatch } = store; + const { dispatch, getState } = store; const { participant } = action; + const { room } = getState()["features/base/conference"]; // we need to first finish dispatching or the notification can be cleared out const result = next(action); const participantDisplayName - = participant && getParticipantDisplayName(store.getState, participant.getId()); + = getParticipantDisplayName(store.getState, participant.getId()); + const roomId = room ?? ""; + + dispatch(hangup(true, roomId, i18next.t("dialog.kickTitle", { participantDisplayName }))); + // Jitsi latest version + // dispatch( + // hangup( + // true, + // roomId, + // participantDisplayName + // ? i18next.t("dialog.kickTitle", { participantDisplayName }) + // : i18next.t("dialog.kickSystemTitle"), + // true + // ) + // ); - dispatch(hangup(true, - participantDisplayName ? i18next.t('dialog.kickTitle', { participantDisplayName }) - : i18next.t('dialog.kickSystemTitle'), - true)); return result; } } From 3434a37a484b8fe1297ded8541bdb784f438e8c8 Mon Sep 17 00:00:00 2001 From: Ramon Candel Segura Date: Thu, 5 Feb 2026 17:44:29 +0100 Subject: [PATCH 14/17] Fix connect --- react/features/base/connection/actions.web.ts | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index a69fb11f29b3..6cbd43676cbe 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -39,26 +39,20 @@ export function connect(id?: string, password?: string) { return getJaasJWT(state); } }) - .then((j) => j && dispatch(setJWT(j))) - .then(() => - dispatch( + .then((j) => { + j && dispatch(setJWT(j)); + return dispatch( _connectInternal({ id, password, name: user?.name, lastname: user?.lastname, isAnonymous: !user, - }) - ) - ) - // latest jitsi changes, test if not works current ones - // .then(j => { - // j && dispatch(setJWT(j)); - - // return dispatch(_connectInternal(id, password)); - // }) - .catch(e => { - logger.error('Connection error', e); + }), + ); + }) + .catch((e) => { + logger.error("Connection error", e); }); } From 10435c21801f04ba37470ce8ff8c32b81635a07b Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Thu, 5 Feb 2026 18:22:08 +0100 Subject: [PATCH 15/17] remove show poor connection warning --- .../middleware.poor-connection.ts | 70 +------------------ 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts b/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts index 04756692314f..c655a1a08886 100644 --- a/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts +++ b/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts @@ -1,54 +1,15 @@ import { AnyAction } from 'redux'; import { IStore } from '../../../../app/types'; -import { showNotification, hideNotification } from '../../../../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../../../../notifications/constants'; +import { hideNotification } from '../../../../notifications/actions'; import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../../../conference/actionTypes'; import { getLocalParticipant } from '../../../participants/functions'; import MiddlewareRegistry from '../../../redux/MiddlewareRegistry'; -import statsEmitter from '../../../../connection-indicator/statsEmitter'; const POOR_CONNECTION_NOTIFICATION_ID = 'connection.poor'; -const GOOD_CONNECTION_THRESHOLD = 30; -const MIN_TIME_BETWEEN_WARNINGS_MS = 60000; -let conferenceJoinTime: number | null = null; -let lastWarningTime: number | null = null; let isNotificationCurrentlyShown = false; let isSubscribedToStats = false; -interface IConnectionStats { - connectionQuality?: number; - bandwidth?: { - download?: number; - upload?: number; - }; - bitrate?: { - download?: number; - upload?: number; - }; -} - -const showPoorConnectionWarning = (store: IStore) => { - const now = Date.now(); - - if (lastWarningTime && (now - lastWarningTime) < MIN_TIME_BETWEEN_WARNINGS_MS) { - return; - } - - store.dispatch( - showNotification( - { - titleKey: 'notify.poorConnection', - descriptionKey: 'notify.poorConnectionDescription', - uid: POOR_CONNECTION_NOTIFICATION_ID, - }, - NOTIFICATION_TIMEOUT_TYPE.LONG - ) - ); - - lastWarningTime = now; - isNotificationCurrentlyShown = true; -}; const hidePoorConnectionWarning = (store: IStore) => { if (!isNotificationCurrentlyShown) { @@ -59,31 +20,7 @@ const hidePoorConnectionWarning = (store: IStore) => { isNotificationCurrentlyShown = false; }; -const checkConnectionQuality = (store: IStore, connectionQuality: number) => { - if (!conferenceJoinTime) { - return; - } - if (connectionQuality < GOOD_CONNECTION_THRESHOLD) { - console.log('TEST: Poor connection detected, showing warning notification. Connection quality:', connectionQuality); - showPoorConnectionWarning(store); - } else { - hidePoorConnectionWarning(store); - } -}; - -const onStatsUpdated = (store: IStore) => (stats: IConnectionStats) => { - if (!conferenceJoinTime) { - return; - } - - const connectionQuality = stats.connectionQuality; - console.log('TEST: Connection stats updated. Connection quality:', connectionQuality); - - if (typeof connectionQuality === 'number') { - checkConnectionQuality(store, connectionQuality); - } -}; MiddlewareRegistry.register((store: IStore) => (next) => (action: AnyAction) => { const result = next(action); @@ -97,12 +34,9 @@ MiddlewareRegistry.register((store: IStore) => (next) => (action: AnyAction) => break; } - conferenceJoinTime = Date.now(); - lastWarningTime = null; isNotificationCurrentlyShown = false; if (localParticipant.id && !isSubscribedToStats) { - statsEmitter.subscribeToClientStats(localParticipant.id, onStatsUpdated(store)); isSubscribedToStats = true; } @@ -112,8 +46,6 @@ MiddlewareRegistry.register((store: IStore) => (next) => (action: AnyAction) => case CONFERENCE_WILL_LEAVE: { // User manually hung up - hide notification and reset state hidePoorConnectionWarning(store); - conferenceJoinTime = null; - lastWarningTime = null; isNotificationCurrentlyShown = false; break; } From 61c8bb8c7c6a03c4052ce29337ca3f5559efb3f9 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Fri, 6 Feb 2026 10:28:15 +0100 Subject: [PATCH 16/17] adjust messages and remove middleware --- lang/main-es.json | 4 +- lang/main.json | 4 +- .../middlewares/connection-stability/index.ts | 5 +- .../middleware.auto-reconnect.ts | 175 ------------------ .../middleware.poor-connection.ts | 57 ------ 5 files changed, 5 insertions(+), 240 deletions(-) delete mode 100644 react/features/base/meet/middlewares/connection-stability/middleware.auto-reconnect.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts diff --git a/lang/main-es.json b/lang/main-es.json index deaf52d65984..ab1424be594c 100644 --- a/lang/main-es.json +++ b/lang/main-es.json @@ -954,8 +954,8 @@ "videoUnmuteBlockedTitle": "¡Desactivar cámara y compartir pantalla bloqueados!", "viewLobby": "Ver lobby", "waitingParticipants": "{{waitingParticipants}} personas", - "encryptionKeySyncFailed": "La sincronización de la clave de cifrado ha fallado. Se recomienda salir de la reunión y vuelva a unirse para restaurar la comunicación segura.", - "encryptionKeySyncFailedTitle": "Error de Sincronización de Cifrado", + "encryptionKeySyncFailed": "El establecimiento de la sesión segura ha fallado. Asegúrese de que todos los participantes tengan una conexión a Internet confiable.", + "encryptionKeySyncFailedTitle": "Error de Establecimiento de la Sesión", "cryptoFailedTitle": "La operación criptográfica ha fallado", "cryptoFailed": "La operación criptográfica ha fallado. No puede acceder al video ni al audio de la reunión. Se recomienda reiniciar el navegador. Si eres el organizador de la reunión, vuelva a crearla.", "encryptionKeySyncRestored": "La sincronización de claves para el cifrado se ha restaurado con éxito. Su comunicación segura está ahora activa.", diff --git a/lang/main.json b/lang/main.json index 683af4d8bdce..6ef59a870b2c 100644 --- a/lang/main.json +++ b/lang/main.json @@ -1096,8 +1096,8 @@ "waitingVisitorsTitle": "The meeting is not live yet!", "whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.", "whiteboardLimitTitle": "Whiteboard usage", - "encryptionKeySyncFailed": "The encryption key synchronization has failed. It is recommended that you leave the meeting and rejoin to restore secure communication.", - "encryptionKeySyncFailedTitle": "Encryption Sync Failed", + "encryptionKeySyncFailed": "The secure session establishement has failed. Please ensure that all participants have a reliable internet connection.", + "encryptionKeySyncFailedTitle": "Session Establishment Failed", "cryptoFailedTitle": "Cryptographic operation failed", "cryptoFailed": "Cryptographic operation has failed. You cannot access video or audio of the meeting. It is recommended that you restart the browser. If you are the meeting organizer, recreate it.", "encryptionKeySyncRestored": "The encryption key synchronization has been successfully restored. Your secure communication is now active.", diff --git a/react/features/base/meet/middlewares/connection-stability/index.ts b/react/features/base/meet/middlewares/connection-stability/index.ts index bacad5c7140c..b75ef7fa89bf 100644 --- a/react/features/base/meet/middlewares/connection-stability/index.ts +++ b/react/features/base/meet/middlewares/connection-stability/index.ts @@ -1,9 +1,7 @@ /** * Collection of middlewares to handle connection stability and user notifications. - * - Poor connection detection when joining meetings * - DataChannel reconnection detection and user notifications * - XMPP/WebSocket connection failure handling (via lib-jitsi-meet events) - * - Automatic reconnection when connection is lost unexpectedly * - Error handling to prevent middleware crashes * - Connection guard to prevent invalid events during disconnection */ @@ -12,8 +10,7 @@ import './middleware.connection-guard'; 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/middlewares/connection-stability/middleware.poor-connection.ts b/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts deleted file mode 100644 index c655a1a08886..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/middleware.poor-connection.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { AnyAction } from 'redux'; -import { IStore } from '../../../../app/types'; -import { hideNotification } from '../../../../notifications/actions'; -import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../../../conference/actionTypes'; -import { getLocalParticipant } from '../../../participants/functions'; -import MiddlewareRegistry from '../../../redux/MiddlewareRegistry'; - -const POOR_CONNECTION_NOTIFICATION_ID = 'connection.poor'; - -let isNotificationCurrentlyShown = false; -let isSubscribedToStats = false; - - -const hidePoorConnectionWarning = (store: IStore) => { - if (!isNotificationCurrentlyShown) { - return; - } - - store.dispatch(hideNotification(POOR_CONNECTION_NOTIFICATION_ID)); - isNotificationCurrentlyShown = false; -}; - - - -MiddlewareRegistry.register((store: IStore) => (next) => (action: AnyAction) => { - const result = next(action); - - switch (action.type) { - case CONFERENCE_JOINED: { - const state = store.getState(); - const localParticipant = getLocalParticipant(state); - - if (!localParticipant) { - break; - } - - isNotificationCurrentlyShown = false; - - if (localParticipant.id && !isSubscribedToStats) { - isSubscribedToStats = true; - } - - break; - } - - case CONFERENCE_WILL_LEAVE: { - // User manually hung up - hide notification and reset state - hidePoorConnectionWarning(store); - isNotificationCurrentlyShown = false; - break; - } - } - - return result; -}); - -export default {}; From 0c1d8b5912477da8ce71167074025c1a9d422774 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Fri, 6 Feb 2026 12:11:42 +0100 Subject: [PATCH 17/17] remove almost all middleware --- .../event-handlers.conference.ts | 50 --- .../event-handlers.connection.ts | 40 --- .../connection-notifications/index.ts | 54 --- .../listener-setup.ts | 69 ---- .../notification-helpers.ts | 63 ---- .../connection-notifications/state.ts | 12 - .../connection-notifications/types.ts | 22 -- .../middlewares/connection-stability/index.ts | 17 - .../middleware.connection-guard.ts | 44 --- .../middleware.datachannel.ts | 179 ---------- .../middleware.error-handling.ts | 16 - react/features/base/meet/middlewares/index.ts | 1 - .../middlewares/meeting.middleware.test.ts | 310 ------------------ 13 files changed, 877 deletions(-) delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.conference.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.connection.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/index.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/listener-setup.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/notification-helpers.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/state.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/connection-notifications/types.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/index.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/middleware.connection-guard.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/middleware.datachannel.ts delete mode 100644 react/features/base/meet/middlewares/connection-stability/middleware.error-handling.ts delete mode 100644 react/features/base/meet/middlewares/meeting.middleware.test.ts diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.conference.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.conference.ts deleted file mode 100644 index ab11bf51fbc2..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.conference.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { IStore } from '../../../../../app/types'; -import { - showConnectionLostNotification, - showConnectionRestoredNotification, - showDeviceSuspendedNotification, -} from './notification-helpers'; -import { ConnectionState } from "./types"; - -/** - * Handles when ICE connection is interrupted (packet loss, network issue) - * User can still see frozen video but quality degrades - * - * @param dispatch - Redux dispatch function - * @param state - Connection state to mark that interruption occurred - */ -export const handleMediaConnectionInterrupted = (dispatch: IStore["dispatch"], state: ConnectionState) => { - console.log("[CONNECTION_NOTIFICATIONS] Media connection interrupted (ICE)"); - state.wasMediaConnectionInterrupted = true; - showConnectionLostNotification(dispatch); -}; - -/** - * Handles when ICE connection is restored after interruption - * Video/audio quality returns to normal - * Only shows notification if there was a previous interruption (not on initial join) - * - * @param dispatch - Redux dispatch function - * @param state - Connection state to check if there was a previous interruption - */ -export const handleMediaConnectionRestored = (dispatch: IStore["dispatch"], state: ConnectionState) => { - console.log("[CONNECTION_NOTIFICATIONS] Media connection restored (ICE)"); - - if (state.wasMediaConnectionInterrupted) { - showConnectionRestoredNotification(dispatch); - state.wasMediaConnectionInterrupted = false; - } else { - console.log("[CONNECTION_NOTIFICATIONS] Skipping notification - no previous interruption"); - } -}; - -/** - * Handles when device is suspended (laptop closed, mobile app backgrounded) - * Connection will be restored when device wakes up - * - * @param dispatch - Redux dispatch function - */ -export const handleDeviceSuspended = (dispatch: IStore['dispatch']) => { - console.log('[CONNECTION_NOTIFICATIONS] Device suspended detected'); - showDeviceSuspendedNotification(dispatch); -}; 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 deleted file mode 100644 index 16110c011ce5..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/event-handlers.connection.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { IStore } from '../../../../../app/types'; -import { isLeavingConferenceManually } from "../../../general/utils/conferenceState"; -import { showConnectionFailedNotification, showConnectionLostNotification } from "./notification-helpers"; - -/** - * Handles when XMPP connection is established - * This is the signaling connection, not media - */ -export const handleXMPPConnected = () => { - console.log("[CONNECTION_NOTIFICATIONS] XMPP connection established"); -}; - -/** - * Handles when XMPP WebSocket is disconnected - * Only shows notification if not a manual disconnect (user clicking hangup) - * - * @param dispatch - Redux dispatch function - * @param message - Disconnect message from lib-jitsi-meet - */ -export const handleXMPPDisconnected = (dispatch: IStore["dispatch"], message: string) => { - console.log("[CONNECTION_NOTIFICATIONS] XMPP disconnected:", message); - - if (isLeavingConferenceManually()) return; - - showConnectionLostNotification(dispatch); -}; - -/** - * Handles when XMPP connection fails to establish or encounters fatal error - * This is more severe than disconnect - connection couldn't be made at all - * - * @param dispatch - Redux dispatch function - * @param error - Error object from lib-jitsi-meet - * @param message - Error message - */ -export const handleXMPPConnectionFailed = (dispatch: IStore["dispatch"], error: any, message: string) => { - console.error("[CONNECTION_NOTIFICATIONS] XMPP connection failed:", error, message); - - showConnectionFailedNotification(dispatch, message); -}; diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/index.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/index.ts deleted file mode 100644 index b464d8ddede8..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { AnyAction } from 'redux'; -import { IStore } from '../../../../../app/types'; -import { CONFERENCE_JOINED, CONFERENCE_WILL_LEAVE } from '../../../../conference/actionTypes'; -import { setLeaveConferenceManually } from '../../../general/utils/conferenceState'; -import { CONNECTION_WILL_CONNECT } from '../../../../connection/actionTypes'; -import MiddlewareRegistry from '../../../../redux/MiddlewareRegistry'; -import { setupConferenceMediaListeners, setupXMPPConnectionListeners } from './listener-setup'; -import { createConnectionState } from './state'; - -/** - * Middleware that listens to Redux actions and sets up lib-jitsi-meet event listeners - * - * Flow: - * 1. CONNECTION_WILL_CONNECT -> Setup XMPP listeners - * 2. CONFERENCE_JOINED -> Setup ICE/media listeners - * 3. CONFERENCE_WILL_LEAVE -> Mark as manual disconnect, cleanup flags - */ -MiddlewareRegistry.register(({ dispatch }: IStore) => { - const connectionState = createConnectionState(); - - return (next: Function) => - (action: AnyAction) => { - const result = next(action); - - switch (action.type) { - case CONNECTION_WILL_CONNECT: { - setLeaveConferenceManually(false); - connectionState.hasConnectionListeners = false; - - const { connection } = action; - - setupXMPPConnectionListeners(connection, dispatch, connectionState); - break; - } - - case CONFERENCE_JOINED: { - const { conference } = action; - - setupConferenceMediaListeners(conference, dispatch, connectionState); - break; - } - - case CONFERENCE_WILL_LEAVE: { - // User clicked hangup button - don't show reconnection notifications - setLeaveConferenceManually(true); - connectionState.hasConferenceListeners = false; - connectionState.wasMediaConnectionInterrupted = false; - break; - } - } - - return result; - }; -}); diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/listener-setup.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/listener-setup.ts deleted file mode 100644 index e133dcc815aa..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/listener-setup.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { IStore } from '../../../../../app/types'; -import { JitsiConferenceEvents, JitsiConnectionEvents } from '../../../../lib-jitsi-meet'; -import { - handleDeviceSuspended, - handleMediaConnectionInterrupted, - handleMediaConnectionRestored, -} from './event-handlers.conference'; -import { - handleXMPPConnected, - handleXMPPConnectionFailed, - handleXMPPDisconnected, -} from './event-handlers.connection'; -import { ConnectionState } from './types'; - -/** - * Attaches event listeners for conference media connection events - * These events track the ICE connection state (actual audio/video transport) - * - * @param conference - Jitsi conference instance - * @param dispatch - Redux dispatch function - * @param state - Connection state to track listener registration - */ -export const setupConferenceMediaListeners = ( - conference: any, - dispatch: IStore["dispatch"], - state: ConnectionState -) => { - if (state.hasConferenceListeners || !conference) { - return; - } - - conference.addEventListener(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => - handleMediaConnectionInterrupted(dispatch, state) - ); - - conference.addEventListener(JitsiConferenceEvents.CONNECTION_RESTORED, () => - handleMediaConnectionRestored(dispatch, state) - ); - - conference.addEventListener(JitsiConferenceEvents.SUSPEND_DETECTED, () => handleDeviceSuspended(dispatch)); - - state.hasConferenceListeners = true; -}; - -/** - * Attaches event listeners for XMPP connection events - * These events track the signaling connection (WebSocket to XMPP server) - * - * @param connection - Jitsi connection instance - * @param dispatch - Redux dispatch function - * @param state - Connection state to track listener registration - */ -export const setupXMPPConnectionListeners = (connection: any, dispatch: IStore["dispatch"], state: ConnectionState) => { - if (!connection || state.hasConnectionListeners) { - return; - } - - connection.addEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED, () => handleXMPPConnected()); - - connection.addEventListener(JitsiConnectionEvents.CONNECTION_DISCONNECTED, (message: string) => - handleXMPPDisconnected(dispatch, message) - ); - - connection.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED, (error: any, message: string) => - handleXMPPConnectionFailed(dispatch, error, message) - ); - - state.hasConnectionListeners = true; -}; diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/notification-helpers.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/notification-helpers.ts deleted file mode 100644 index 0af1999255b8..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/notification-helpers.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { IStore } from '../../../../../app/types'; -import { showNotification, showWarningNotification } from '../../../../../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../../../../../notifications/constants'; - -/** - * Shows a "Connection lost" warning notification with reconnection message - */ -export const showConnectionLostNotification = (dispatch: IStore['dispatch']) => { - dispatch( - showWarningNotification( - { - titleKey: 'notify.connectionLost', - descriptionKey: 'notify.reconnecting', - }, - NOTIFICATION_TIMEOUT_TYPE.LONG - ) - ); -}; - -/** - * Shows a "Connected" success notification when connection is restored - */ -export const showConnectionRestoredNotification = (dispatch: IStore['dispatch']) => { - dispatch( - showNotification( - { - titleKey: 'notify.connectedTitle', - descriptionKey: 'notify.connectedMessage', - }, - NOTIFICATION_TIMEOUT_TYPE.SHORT - ) - ); -}; - -/** - * Shows a "Connection failed" error notification with custom message - */ -export const showConnectionFailedNotification = (dispatch: IStore['dispatch'], errorMessage?: string) => { - dispatch( - showWarningNotification( - { - titleKey: 'notify.connectionFailed', - ...(errorMessage && { descriptionKey: errorMessage }), - }, - NOTIFICATION_TIMEOUT_TYPE.LONG - ) - ); -}; - -/** - * Shows a "Device suspended" warning notification - */ -export const showDeviceSuspendedNotification = (dispatch: IStore['dispatch']) => { - dispatch( - showWarningNotification( - { - titleKey: 'notify.connectionLost', - descriptionKey: 'notify.deviceSuspended', - }, - NOTIFICATION_TIMEOUT_TYPE.LONG - ) - ); -}; diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/state.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/state.ts deleted file mode 100644 index 6a75107177aa..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/state.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ConnectionState } from './types'; - -/** - * Factory function to create a new connection state instance - * - * @returns A new ConnectionState object with default values - */ -export const createConnectionState = (): ConnectionState => ({ - hasConferenceListeners: false, - hasConnectionListeners: false, - wasMediaConnectionInterrupted: false, -}); diff --git a/react/features/base/meet/middlewares/connection-stability/connection-notifications/types.ts b/react/features/base/meet/middlewares/connection-stability/connection-notifications/types.ts deleted file mode 100644 index 48a615197a3f..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/connection-notifications/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * State interface for tracking connection notification behavior - */ -export interface ConnectionState { - /** - * True when conference media listeners (ICE/Media events) have been attached - * Prevents duplicate event listener registration - */ - hasConferenceListeners: boolean; - - /** - * True when connection listeners (XMPP/WebSocket events) have been attached - * Prevents duplicate event listener registration - */ - hasConnectionListeners: boolean; - - /** - * True when media connection (ICE) was interrupted - * Used to only show "connection restored" notification if there was a previous interruption - */ - wasMediaConnectionInterrupted: boolean; -} diff --git a/react/features/base/meet/middlewares/connection-stability/index.ts b/react/features/base/meet/middlewares/connection-stability/index.ts deleted file mode 100644 index b75ef7fa89bf..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Collection of middlewares to handle connection stability and user notifications. - * - DataChannel reconnection detection and user notifications - * - XMPP/WebSocket connection failure handling (via lib-jitsi-meet events) - * - Error handling to prevent middleware crashes - * - Connection guard to prevent invalid events during disconnection - */ - -import './middleware.connection-guard'; -import './connection-notifications'; -import './middleware.datachannel'; -import './middleware.error-handling'; - - -export { }; - -console.log('Connection stability middlewares loaded'); \ No newline at end of file diff --git a/react/features/base/meet/middlewares/connection-stability/middleware.connection-guard.ts b/react/features/base/meet/middlewares/connection-stability/middleware.connection-guard.ts deleted file mode 100644 index 1de40c966a56..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/middleware.connection-guard.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AnyAction } from "redux"; -import { DOMINANT_SPEAKER_CHANGED } from "../../../participants/actionTypes"; -import MiddlewareRegistry from "../../../redux/MiddlewareRegistry"; - -/** - * Middleware to prevent dominant speaker events when connection is unstable. - * This prevents the cascade of "Not connected" errors that kick users out. - */ -MiddlewareRegistry.register((store) => (next) => (action: AnyAction) => { - if (action.type === DOMINANT_SPEAKER_CHANGED) { - const state = store.getState(); - const { connection } = state["features/base/connection"]; - const { conference } = state["features/base/conference"]; - - // Check 1: Connection must exist - if (!connection) { - console.warn("Dominant speaker event suppressed - no connection"); - return action; - } - - // Check 2: Conference must exist and be joined - if (!conference) { - console.warn("Dominant speaker event suppressed - no conference"); - return action; - } - - // Check 3: Connection must be in a good state (has jitsi id) - try { - const isConnectionReady = connection && typeof connection.getJid === "function"; - if (isConnectionReady) { - const jid = connection.getJid(); - if (!jid) { - console.warn("Dominant speaker event suppressed - connection not ready (no JID)"); - return action; - } - } - } catch (error) { - console.warn("Dominant speaker event suppressed - connection error:", error); - return action; - } - } - - return next(action); -}); diff --git a/react/features/base/meet/middlewares/connection-stability/middleware.datachannel.ts b/react/features/base/meet/middlewares/connection-stability/middleware.datachannel.ts deleted file mode 100644 index bd2efd10e74a..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/middleware.datachannel.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { AnyAction } from 'redux'; -import { IStore } from '../../../../app/types'; -import { hideNotification, showNotification } from '../../../../notifications/actions'; -import { NOTIFICATION_TIMEOUT_TYPE } from '../../../../notifications/constants'; -import { DATA_CHANNEL_CLOSED, DATA_CHANNEL_OPENED } from '../../../conference/actionTypes'; -import MiddlewareRegistry from '../../../redux/MiddlewareRegistry'; - -// Notification IDs -const DATACHANNEL_RECONNECTION_NOTIFICATION_ID = 'datachannel.reconnecting'; -const DATACHANNEL_FAILED_NOTIFICATION_ID = 'datachannel.failed'; - -// Timing constants -const RECONNECTION_TIMEOUT_MS = 15000; -const RESET_COUNTER_TIMEOUT_MS = 60000; -const PERSISTENT_ISSUE_THRESHOLD = 3; - -// State tracking -let isDataChannelClosed = false; -let dataChannelCloseCount = 0; -let resetCounterTimer: number | null = null; -let reconnectionTimeoutTimer: number | null = null; - -/** - * Clears all active timers. - */ -const clearAllTimers = () => { - if (resetCounterTimer) { - clearTimeout(resetCounterTimer); - resetCounterTimer = null; - } - if (reconnectionTimeoutTimer) { - clearTimeout(reconnectionTimeoutTimer); - reconnectionTimeoutTimer = null; - } -}; - -/** - * Resets the data channel state to initial values. - */ -const resetDataChannelState = () => { - dataChannelCloseCount = 0; - isDataChannelClosed = false; -}; - -/** - * Shows the initial data channel closed notification. - */ -const showDataChannelClosedNotification = (store: IStore) => { - store.dispatch( - showNotification( - { - titleKey: "notify.dataChannelClosed", - descriptionKey: "notify.dataChannelClosedDescription", - uid: DATACHANNEL_RECONNECTION_NOTIFICATION_ID, - }, - NOTIFICATION_TIMEOUT_TYPE.STICKY - ) - ); -}; - -/** - * Shows the reconnection failed notification after timeout. - */ -const showReconnectionFailedNotification = (store: IStore) => { - store.dispatch(hideNotification(DATACHANNEL_RECONNECTION_NOTIFICATION_ID)); - store.dispatch( - showNotification( - { - titleKey: "dialog.conferenceDisconnectTitle", - descriptionKey: "dialog.conferenceDisconnectMsg", - uid: DATACHANNEL_FAILED_NOTIFICATION_ID, - }, - NOTIFICATION_TIMEOUT_TYPE.STICKY - ) - ); -}; - -/** - * Shows the persistent issue notification when channel closes multiple times. - */ -const showPersistentIssueNotification = (store: IStore) => { - store.dispatch(hideNotification(DATACHANNEL_RECONNECTION_NOTIFICATION_ID)); - store.dispatch( - showNotification( - { - titleKey: "notify.connectionFailed", - descriptionKey: "dialog.conferenceDisconnectMsg", - uid: DATACHANNEL_FAILED_NOTIFICATION_ID, - }, - NOTIFICATION_TIMEOUT_TYPE.STICKY - ) - ); -}; - -/** - * Hides all data channel notifications. - */ -const hideAllNotifications = (store: IStore) => { - store.dispatch(hideNotification(DATACHANNEL_RECONNECTION_NOTIFICATION_ID)); - store.dispatch(hideNotification(DATACHANNEL_FAILED_NOTIFICATION_ID)); -}; - -/** - * Schedules a timeout to detect reconnection failure. - */ -const scheduleReconnectionTimeout = (store: IStore) => { - if (reconnectionTimeoutTimer) { - clearTimeout(reconnectionTimeoutTimer); - } - reconnectionTimeoutTimer = setTimeout(() => { - if (isDataChannelClosed) { - console.error(`Data channel failed to reconnect after ${RECONNECTION_TIMEOUT_MS / 1000} seconds`); - showReconnectionFailedNotification(store); - } - }, RECONNECTION_TIMEOUT_MS); -}; - -/** - * Schedules a timeout to reset the close counter. - */ -const scheduleCounterReset = () => { - if (resetCounterTimer) { - clearTimeout(resetCounterTimer); - } - resetCounterTimer = setTimeout(() => { - dataChannelCloseCount = 0; - }, RESET_COUNTER_TIMEOUT_MS); -}; - -/** - * Middleware to handle BridgeChannel (DataChannel) connection issues. - * When the DataChannel closes unexpectedly, notify the user. - * lib-jitsi-meet will automatically attempt to reconnect the datachannel. - */ -MiddlewareRegistry.register((store) => (next) => (action: AnyAction) => { - const result = next(action); - - switch (action.type) { - case DATA_CHANNEL_OPENED: { - console.log("Data channel opened successfully"); - - clearAllTimers(); - resetDataChannelState(); - - if (isDataChannelClosed) { - hideAllNotifications(store); - } - - break; - } - - case DATA_CHANNEL_CLOSED: { - const { code, reason } = action; - console.warn(`Data channel closed unexpectedly - code: ${code}, reason: ${reason}`); - - dataChannelCloseCount++; - isDataChannelClosed = true; - - const isPersistentIssue = dataChannelCloseCount >= PERSISTENT_ISSUE_THRESHOLD; - - if (isPersistentIssue) { - console.error(`Data channel closed ${dataChannelCloseCount} times, persistent connection issue`); - showPersistentIssueNotification(store); - } else { - showDataChannelClosedNotification(store); - scheduleReconnectionTimeout(store); - } - - scheduleCounterReset(); - - break; - } - } - - return result; -}); - -// Export something to prevent tree-shaking -export default {}; diff --git a/react/features/base/meet/middlewares/connection-stability/middleware.error-handling.ts b/react/features/base/meet/middlewares/connection-stability/middleware.error-handling.ts deleted file mode 100644 index 9dc88606b0c4..000000000000 --- a/react/features/base/meet/middlewares/connection-stability/middleware.error-handling.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AnyAction } from 'redux'; -import MiddlewareRegistry from '../../../redux/MiddlewareRegistry'; - -/** - * Middleware to handle errors gracefully during action processing. - * Prevents middleware crashes from breaking the entire Redux flow. - */ -MiddlewareRegistry.register(() => next => (action: AnyAction) => { - try { - return next(action); - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error('Middleware error:', errorMessage, 'for action:', action.type); - return undefined; - } -}); diff --git a/react/features/base/meet/middlewares/index.ts b/react/features/base/meet/middlewares/index.ts index bcc0dd6f96de..5a70a5bfac2b 100644 --- a/react/features/base/meet/middlewares/index.ts +++ b/react/features/base/meet/middlewares/index.ts @@ -1,3 +1,2 @@ export * from "./meeting.middleware"; -import './connection-stability'; diff --git a/react/features/base/meet/middlewares/meeting.middleware.test.ts b/react/features/base/meet/middlewares/meeting.middleware.test.ts deleted file mode 100644 index fcb58481c4c6..000000000000 --- a/react/features/base/meet/middlewares/meeting.middleware.test.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import MiddlewareRegistry from "../../redux/MiddlewareRegistry"; -import { setPlanName, setUserTier, updateMeetingConfig } from "../general/store/meeting/actions"; -import { MEETING_REDUCER } from "../general/store/meeting/reducer"; -import { LocalStorageManager } from "../LocalStorageManager"; -import { PaymentsService } from "../services/payments.service"; -import { AUTH_ACTIONS, meetingConfigMiddleware } from "./meeting.middleware"; - -vi.mock("../../redux/MiddlewareRegistry", () => ({ - default: { - register: vi.fn(), - }, -})); - -vi.mock("../general/store/meeting/actions", () => ({ - updateMeetingConfig: vi.fn((config) => ({ - type: "UPDATE_MEETING_CONFIG", - payload: config, - })), - setPlanName: vi.fn((planName) => ({ - type: "SET_PLAN_NAME", - payload: { planName }, - })), - setUserTier: vi.fn((userTier) => ({ - type: "SET_USER_TIER", - payload: { userTier }, - })), -})); - -vi.mock("../LocalStorageManager", () => { - const mockInstance = { - get: vi.fn(), - set: vi.fn(), - clearStorage: vi.fn(), - clearCredentials: vi.fn(), - getUser: vi.fn(), - getToken: vi.fn(), - }; - return { - default: mockInstance, - LocalStorageManager: { instance: mockInstance }, - STORAGE_KEYS: { - LAST_CONFIG_CHECK: "lastMeetingConfigCheck", - CACHED_MEETING_CONFIG: "cachedMeetingConfig", - LAST_USER_REFRESH: "lastUserRefresh", - }, - }; -}); - -vi.mock("../services/payments.service", () => ({ - PaymentsService: { - instance: { - getUserTier: vi.fn(), - checkMeetAvailability: vi.fn(), - }, - }, -})); - -// TODO: UNCOMMENT COMMENTED TESTS WHEN MEET BACKEND IS READY -describe("meetingConfigMiddleware", () => { - const originalConsoleError = console.error; - const originalConsoleInfo = console.info; - - const dispatchMock = vi.fn(); - const nextMock = vi.fn((action) => action); - const getStateMock = vi.fn(); - const storeMock = { - dispatch: dispatchMock, - getState: getStateMock, - }; - - const sampleUserTier = { - id: "tier-123", - label: "premium", - productId: "product-456", - billingType: "subscription" as const, - featuresPerService: { - meet: { - enabled: true, - paxPerCall: 10, - }, - drive: {}, - backups: {}, - antivirus: {}, - mail: {}, - vpn: {}, - cleaner: {}, - }, - }; - - beforeEach(() => { - console.error = vi.fn(); - console.info = vi.fn(); - - vi.clearAllMocks(); - - vi.spyOn(Date, "now").mockImplementation(() => 1600000000000); - - (LocalStorageManager.instance.get as ReturnType).mockReturnValue(0); - - (PaymentsService.instance.getUserTier as ReturnType).mockResolvedValue(sampleUserTier); - - getStateMock.mockReturnValue({ - [MEETING_REDUCER]: { - enabled: false, - }, - }); - }); - - afterEach(() => { - console.error = originalConsoleError; - console.info = originalConsoleInfo; - }); - - describe("Middleware Registration", () => { - it("When middleware is initialized, then it should register with MiddlewareRegistry", () => { - MiddlewareRegistry.register(meetingConfigMiddleware); - expect(MiddlewareRegistry.register).toHaveBeenCalledWith(meetingConfigMiddleware); - }); - }); - - describe("Action Handling", () => { - it("When any action is passed, then it should pass the action to next middleware", () => { - const action = { type: "TEST_ACTION" }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - middleware(action); - expect(nextMock).toHaveBeenCalledWith(action); - }); - }); - - describe("LOGIN_SUCCESS Action", () => { - it("When LOGIN_SUCCESS action is dispatched, then it should update meeting config and plan name", async () => { - const action = { type: AUTH_ACTIONS.LOGIN_SUCCESS }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - middleware(action); - - await vi.waitFor(() => { - expect(PaymentsService.instance.getUserTier).toHaveBeenCalled(); - }); - - expect(PaymentsService.instance.getUserTier).toHaveBeenCalledTimes(1); - expect(updateMeetingConfig).toHaveBeenCalledWith({ - enabled: true, - paxPerCall: 10, - }); - expect(setPlanName).toHaveBeenCalledWith("premium"); - expect(setUserTier).toHaveBeenCalledWith(sampleUserTier); - expect(LocalStorageManager.instance.set).toHaveBeenCalledWith("lastMeetingConfigCheck", expect.any(Number)); - }); - - it("When LOGIN_SUCCESS action is dispatched and last check was recent, then it should still force update", async () => { - const action = { type: AUTH_ACTIONS.LOGIN_SUCCESS }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - - const recentCheckTime = Date.now() - 1000; - (LocalStorageManager.instance.get as ReturnType).mockReturnValue(recentCheckTime); - - middleware(action); - - await vi.waitFor(() => { - expect(PaymentsService.instance.getUserTier).toHaveBeenCalled(); - }); - - expect(PaymentsService.instance.getUserTier).toHaveBeenCalled(); - }); - }); - - describe("REFRESH_TOKEN_SUCCESS Action", () => { - it("When REFRESH_TOKEN_SUCCESS action is dispatched and interval has expired, then it should update meeting config", async () => { - const action = { type: AUTH_ACTIONS.REFRESH_TOKEN_SUCCESS }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - - const oldCheckTime = Date.now() - 61 * 60 * 1000; - (LocalStorageManager.instance.get as ReturnType).mockReturnValue(oldCheckTime); - - middleware(action); - - await vi.waitFor(() => { - expect(PaymentsService.instance.getUserTier).toHaveBeenCalled(); - }); - - expect(updateMeetingConfig).toHaveBeenCalled(); - expect(setPlanName).toHaveBeenCalled(); - expect(setUserTier).toHaveBeenCalled(); - }); - - it("When REFRESH_TOKEN_SUCCESS action is dispatched and interval has not expired, then it should not update meeting config", async () => { - const action = { type: AUTH_ACTIONS.REFRESH_TOKEN_SUCCESS }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - - const recentCheckTime = Date.now() - 1000; - (LocalStorageManager.instance.get as ReturnType).mockReturnValue(recentCheckTime); - - middleware(action); - - await vi.waitFor(() => {}, { timeout: 100 }); - - expect(PaymentsService.instance.getUserTier).not.toHaveBeenCalled(); - }); - }); - - describe("INITIALIZE_AUTH Action", () => { - it("When INITIALIZE_AUTH action is dispatched with authenticated user and meeting not enabled, then it should force update", async () => { - const action = { - type: AUTH_ACTIONS.INITIALIZE_AUTH, - payload: { isAuthenticated: true }, - }; - - getStateMock.mockReturnValue({ - [MEETING_REDUCER]: { - enabled: false, - }, - }); - - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - middleware(action); - - await vi.waitFor(() => { - expect(PaymentsService.instance.getUserTier).toHaveBeenCalled(); - }); - - expect(updateMeetingConfig).toHaveBeenCalled(); - expect(setPlanName).toHaveBeenCalled(); - expect(setUserTier).toHaveBeenCalled(); - }); - - it("When INITIALIZE_AUTH action is dispatched with authenticated user, meeting enabled, and interval not expired, then it should not update", async () => { - const action = { - type: AUTH_ACTIONS.INITIALIZE_AUTH, - payload: { isAuthenticated: true }, - }; - - getStateMock.mockReturnValue({ - [MEETING_REDUCER]: { - enabled: true, - }, - }); - - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - - const recentCheckTime = Date.now() - 1000; - (LocalStorageManager.instance.get as ReturnType).mockReturnValue(recentCheckTime); - - middleware(action); - - await vi.waitFor(() => {}, { timeout: 100 }); - - expect(PaymentsService.instance.getUserTier).not.toHaveBeenCalled(); - }); - - it("When INITIALIZE_AUTH action is dispatched with non-authenticated user, then it should not update meeting config", async () => { - const action = { - type: AUTH_ACTIONS.INITIALIZE_AUTH, - payload: { isAuthenticated: false }, - }; - - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - middleware(action); - - await vi.waitFor(() => {}, { timeout: 100 }); - - expect(PaymentsService.instance.getUserTier).not.toHaveBeenCalled(); - }); - }); - - describe("LOGOUT Action", () => { - it("When LOGOUT action is dispatched, then it should clear storage and credentials", () => { - const action = { type: AUTH_ACTIONS.LOGOUT }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - middleware(action); - - expect(LocalStorageManager.instance.clearCredentials).toHaveBeenCalledTimes(1); - }); - - it("When LOGOUT action is dispatched and clearStorage fails, then it should handle the error", () => { - const action = { type: AUTH_ACTIONS.LOGOUT }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - - vi.spyOn(LocalStorageManager.instance, 'clearStorage' as any).mockImplementation(() => { - throw new Error("localStorage error"); - }); - - middleware(action); - - expect(console.error).toHaveBeenCalledWith( - "Error clearing cached data from localStorage", - expect.any(Error) - ); - }); - }); - - describe("Error Handling", () => { - it("When updating meeting config and API call fails, then it should handle the error", async () => { - const action = { type: AUTH_ACTIONS.LOGIN_SUCCESS }; - const middleware = meetingConfigMiddleware(storeMock)(nextMock); - - (PaymentsService.instance.getUserTier as ReturnType).mockRejectedValue( - new Error("API error") - ); - - middleware(action); - - await vi.waitFor(() => { - expect(console.error).toHaveBeenCalled(); - }); - - expect(console.error).toHaveBeenCalledWith("Error checking meeting configuration", expect.any(Error)); - }); - }); -});