From 6742435487aa4314cbce9616ea7905bdccd4c3ac Mon Sep 17 00:00:00 2001 From: Hristo Terezov Date: Wed, 9 Jul 2025 14:48:21 -0500 Subject: [PATCH] fix: GUM prompt not displayed after deeplinking page. When we open a custom scheme URL before the window load event has been fired it seems that GUM prompt is not displayed after this due to Chrome bug. See more details here https://issues.chromium.org/issues/41398687. The result in Jitsi Meet is the following: If the user is joining a call for first time and haven't granted A/V permissions and lands on the deeplinking page we try to open the desktop app via redirect to a custom scheme URL. If the user chooses cancel and "Launch in web" we go to the prejoin screen and proceed with the initial GUM. At this point any GUM call won't display the permission prompt due to the browser bug and will go on forever making it impossible for the user to unmute camera or microphone. --- react/features/app/functions.native.ts | 16 ++++++++++++ react/features/app/functions.web.ts | 26 +++++++++++++++++++ react/features/deep-linking/openDesktopApp.ts | 6 ++++- react/index.web.js | 4 +++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/react/features/app/functions.native.ts b/react/features/app/functions.native.ts index 8743fa202a..513f48852e 100644 --- a/react/features/app/functions.native.ts +++ b/react/features/app/functions.native.ts @@ -5,6 +5,7 @@ import { toState } from '../base/redux/functions'; import { getServerURL } from '../base/settings/functions.native'; export * from './functions.any'; +import logger from './logger'; /** * Retrieves the default URL for the app. This can either come from a prop to @@ -38,3 +39,18 @@ export function getName() { export function getSdkBundlePath() { return NativeModules.AppInfo.sdkBundlePath; } + +/** + * This is a dummy implementation for compatibility with web. It executes the passed handler right away. + * + * @param {Function} handler - The callback function to execute. + * @returns {void} + */ +export function executeAfterLoad(handler: () => void) { + try { + handler(); + } catch (error) { + logger.error('Error executing handler after load:', error); + } +} + diff --git a/react/features/app/functions.web.ts b/react/features/app/functions.web.ts index 804fd4170f..4771e9366f 100644 --- a/react/features/app/functions.web.ts +++ b/react/features/app/functions.web.ts @@ -1,8 +1,10 @@ import { IStateful } from '../base/app/types'; import { toState } from '../base/redux/functions'; import { getServerURL } from '../base/settings/functions.web'; +import { getJitsiMeetGlobalNS } from '../base/util/helpers'; export * from './functions.any'; +import logger from './logger'; /** * Retrieves the default URL for the app. This can either come from a prop to @@ -31,3 +33,27 @@ export function getDefaultURL(stateful: IStateful) { export function getName() { return interfaceConfig.APP_NAME; } + +/** + * Executes a handler function after the window load event has been received. + * If the app has already loaded, the handler is executed immediately. + * Otherwise, the handler is registered as a 'load' event listener. + * + * @param {Function} handler - The callback function to execute. + * @returns {void} + */ +export function executeAfterLoad(handler: () => void) { + const safeHandler = () => { + try { + handler(); + } catch (error) { + logger.error('Error executing handler after load:', error); + } + }; + + if (getJitsiMeetGlobalNS()?.hasLoaded) { + safeHandler(); + } else { + window.addEventListener('load', safeHandler); + } +} diff --git a/react/features/deep-linking/openDesktopApp.ts b/react/features/deep-linking/openDesktopApp.ts index 8b7d19a131..3793dd5b29 100644 --- a/react/features/deep-linking/openDesktopApp.ts +++ b/react/features/deep-linking/openDesktopApp.ts @@ -1,3 +1,4 @@ +import { executeAfterLoad } from '../app/functions'; import { IReduxState } from '../app/types'; import { URI_PROTOCOL_PATTERN } from '../base/util/uri'; @@ -16,7 +17,10 @@ export function _openDesktopApp(_state: Object) { const { appScheme } = deeplinkingDesktop; const regex = new RegExp(URI_PROTOCOL_PATTERN, 'gi'); - window.location.href = window.location.href.replace(regex, `${appScheme}:`); + // This is needed to workaround https://issues.chromium.org/issues/41398687 + executeAfterLoad(() => { + window.location.href = window.location.href.replace(regex, `${appScheme}:`); + }); return Promise.resolve(true); } diff --git a/react/index.web.js b/react/index.web.js index 361e7ef985..2f05f2dfd4 100644 --- a/react/index.web.js +++ b/react/index.web.js @@ -47,11 +47,15 @@ if (Platform.OS === 'ios') { const globalNS = getJitsiMeetGlobalNS(); const connectionTimes = getJitsiMeetGlobalNSConnectionTimes(); +// Used to check if the load event has been fired. +globalNS.hasLoaded = false; + // Used for automated performance tests. connectionTimes['index.loaded'] = window.indexLoadedTime; window.addEventListener('load', () => { connectionTimes['window.loaded'] = window.loadedEventTime; + globalNS.hasLoaded = true; }); document.addEventListener('DOMContentLoaded', () => {