From 1e8cc9d085a7fc06157fd860c7e4ecb8ccd52667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Fri, 7 Mar 2025 13:48:16 +0100 Subject: [PATCH] feat(inIframe/isEmbedded) add ability to detect generic embedding On web we detect if we run on an iframe, and on mobile we detect if the app is one of ours. --- index.html | 4 +-- react/features/analytics/functions.ts | 4 +-- react/features/app/actions.web.ts | 4 +-- react/features/app/components/App.native.tsx | 3 +- react/features/app/middleware.ts | 4 +-- react/features/base/app/middleware.web.ts | 4 +-- react/features/base/config/configWhitelist.ts | 6 ++-- .../base/config/inIframeConfigWhitelist.ts | 4 --- .../inIframeInterfaceConfigWhitelist.ts | 5 --- .../base/config/interfaceConfigWhitelist.ts | 6 ++-- .../base/config/isEmbeddedConfigWhitelist.ts | 11 +++++++ .../isEmbeddedInterfaceConfigWhitelist.ts | 6 ++++ react/features/base/connection/actions.any.ts | 4 +-- .../base/jitsi-local-storage/setup.web.ts | 4 +-- react/features/base/util/embedUtils.native.ts | 32 +++++++++++++++++++ .../{iframeUtils.ts => embedUtils.web.ts} | 6 +--- react/features/conference/middleware.any.ts | 7 ++-- react/features/recent-list/middleware.ts | 6 ++-- .../Recording/LocalRecordingManager.web.ts | 4 +-- 19 files changed, 81 insertions(+), 43 deletions(-) delete mode 100644 react/features/base/config/inIframeConfigWhitelist.ts delete mode 100644 react/features/base/config/inIframeInterfaceConfigWhitelist.ts create mode 100644 react/features/base/config/isEmbeddedConfigWhitelist.ts create mode 100644 react/features/base/config/isEmbeddedInterfaceConfigWhitelist.ts create mode 100644 react/features/base/util/embedUtils.native.ts rename react/features/base/util/{iframeUtils.ts => embedUtils.web.ts} (66%) diff --git a/index.html b/index.html index bd30c55d5e..c624ca3ad7 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@ Component: JitsiMeetJS.app.entryPoints.APP }) - const inIframe = () => { + const isEmbedded = () => { try { return window.self !== window.top; } catch (e) { @@ -45,7 +45,7 @@ }; const isElectron = navigator.userAgent.includes('Electron'); - const shouldRegisterWorker = !isElectron && !inIframe() && 'serviceWorker' in navigator; + const shouldRegisterWorker = !isElectron && !isEmbedded() && 'serviceWorker' in navigator; if (shouldRegisterWorker) { navigator.serviceWorker diff --git a/react/features/analytics/functions.ts b/react/features/analytics/functions.ts index 94cedd3bd2..355ab4e603 100644 --- a/react/features/analytics/functions.ts +++ b/react/features/analytics/functions.ts @@ -12,8 +12,8 @@ import JitsiMeetJS, { browser } from '../base/lib-jitsi-meet'; import { isAnalyticsEnabled } from '../base/lib-jitsi-meet/functions.any'; +import { isEmbedded } from '../base/util/embedUtils'; import { getJitsiMeetGlobalNS } from '../base/util/helpers'; -import { inIframe } from '../base/util/iframeUtils'; import { loadScript } from '../base/util/loadScript'; import { parseURLParams } from '../base/util/parseURLParams'; import { parseURIString } from '../base/util/uri'; @@ -213,7 +213,7 @@ export function initAnalytics(store: IStore, handlers: Array): boolean { permanentProperties.externalApi = typeof API_ID === 'number'; // Report if we are loaded in iframe - permanentProperties.inIframe = inIframe(); + permanentProperties.inIframe = isEmbedded(); // Report the tenant from the URL. permanentProperties.tenant = tenant || '/'; diff --git a/react/features/app/actions.web.ts b/react/features/app/actions.web.ts index 476b4148f0..0c67f119f4 100644 --- a/react/features/app/actions.web.ts +++ b/react/features/app/actions.web.ts @@ -7,7 +7,7 @@ import { } from '../base/config/actions'; import { setLocationURL } from '../base/connection/actions.web'; import { loadConfig } from '../base/lib-jitsi-meet/functions.web'; -import { inIframe } from '../base/util/iframeUtils'; +import { isEmbedded } from '../base/util/embedUtils'; import { parseURIString } from '../base/util/uri'; import { isVpaasMeeting } from '../jaas/functions'; import { clearNotifications, showNotification } from '../notifications/actions'; @@ -102,7 +102,7 @@ export function maybeRedirectToWelcomePage(options: { feedbackSubmitted?: boolea // if close page is enabled redirect to it, without further action if (enableClosePage) { if (isVpaasMeeting(getState())) { - const isOpenedInIframe = inIframe(); + const isOpenedInIframe = isEmbedded(); if (isOpenedInIframe) { // @ts-ignore diff --git a/react/features/app/components/App.native.tsx b/react/features/app/components/App.native.tsx index 725267fe36..581a31eab8 100644 --- a/react/features/app/components/App.native.tsx +++ b/react/features/app/components/App.native.tsx @@ -12,6 +12,7 @@ import { clientResized, setSafeAreaInsets } from '../../base/responsive-ui/actio import DimensionsDetector from '../../base/responsive-ui/components/DimensionsDetector.native'; import { updateSettings } from '../../base/settings/actions'; import JitsiThemePaperProvider from '../../base/ui/components/JitsiThemeProvider.native'; +import { isEmbedded } from '../../base/util/embedUtils.native'; import { _getRouteToRender } from '../getRouteToRender.native'; import logger from '../logger'; @@ -87,7 +88,7 @@ export class App extends AbstractApp { const liteTxt = AppInfo.isLiteSDK ? ' (lite)' : ''; - logger.info(`Loaded SDK ${AppInfo.sdkVersion}${liteTxt}`); + logger.info(`Loaded SDK ${AppInfo.sdkVersion}${liteTxt} isEmbedded=${isEmbedded()}`); } /** diff --git a/react/features/app/middleware.ts b/react/features/app/middleware.ts index 8fcbdc7c0c..f2917e4255 100644 --- a/react/features/app/middleware.ts +++ b/react/features/app/middleware.ts @@ -7,7 +7,7 @@ import { SET_ROOM } from '../base/conference/actionTypes'; import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes'; import { getURLWithoutParams } from '../base/connection/utils'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; -import { inIframe } from '../base/util/iframeUtils'; +import { isEmbedded } from '../base/util/embedUtils'; import { reloadNow } from './actions'; import { _getRouteToRender } from './getRouteToRender'; @@ -52,7 +52,7 @@ function _connectionEstablished(store: IStore, next: Function, action: AnyAction // @ts-ignore const { history, location } = window; - if (inIframe()) { + if (isEmbedded()) { return; } diff --git a/react/features/base/app/middleware.web.ts b/react/features/base/app/middleware.web.ts index ece05d1f0d..a91492b467 100644 --- a/react/features/base/app/middleware.web.ts +++ b/react/features/base/app/middleware.web.ts @@ -1,7 +1,7 @@ import { AnyAction } from 'redux'; import MiddlewareRegistry from '../redux/MiddlewareRegistry'; -import { inIframe } from '../util/iframeUtils'; +import { isEmbedded } from '../util/embedUtils'; import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes'; import logger from './logger'; @@ -24,7 +24,7 @@ MiddlewareRegistry.register(() => (next: Function) => (action: AnyAction) => { case APP_WILL_MOUNT: { // Disable it inside an iframe until Google fixes the origin trial for 3rd party sources: // https://bugs.chromium.org/p/chromium/issues/detail?id=1504167 - if (!inIframe() && 'PressureObserver' in globalThis) { + if (!isEmbedded() && 'PressureObserver' in globalThis) { pressureObserver = new window.PressureObserver( (records: typeof window.PressureRecord) => { logger.info('Compute pressure state changed:', JSON.stringify(records)); diff --git a/react/features/base/config/configWhitelist.ts b/react/features/base/config/configWhitelist.ts index b919ffb38e..abffe3cfd5 100644 --- a/react/features/base/config/configWhitelist.ts +++ b/react/features/base/config/configWhitelist.ts @@ -1,7 +1,7 @@ -import { inIframe } from '../util/iframeUtils'; +import { isEmbedded } from '../util/embedUtils'; import extraConfigWhitelist from './extraConfigWhitelist'; -import inIframeConfigWhitelist from './inIframeConfigWhitelist'; +import isEmbeddedConfigWhitelist from './isEmbeddedConfigWhitelist'; /** * The config keys to whitelist, the keys that can be overridden. @@ -249,4 +249,4 @@ export default [ 'webrtcIceTcpDisable', 'webrtcIceUdpDisable', 'whiteboard.enabled' -].concat(extraConfigWhitelist).concat(inIframe() ? inIframeConfigWhitelist : []); +].concat(extraConfigWhitelist).concat(isEmbedded() ? isEmbeddedConfigWhitelist : []); diff --git a/react/features/base/config/inIframeConfigWhitelist.ts b/react/features/base/config/inIframeConfigWhitelist.ts deleted file mode 100644 index be1917a305..0000000000 --- a/react/features/base/config/inIframeConfigWhitelist.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Additional config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an iframe. - */ -export default []; diff --git a/react/features/base/config/inIframeInterfaceConfigWhitelist.ts b/react/features/base/config/inIframeInterfaceConfigWhitelist.ts deleted file mode 100644 index a50628adad..0000000000 --- a/react/features/base/config/inIframeInterfaceConfigWhitelist.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Additional interface config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an - * iframe. - */ -export default []; diff --git a/react/features/base/config/interfaceConfigWhitelist.ts b/react/features/base/config/interfaceConfigWhitelist.ts index ae97fc8535..cbf3ce8198 100644 --- a/react/features/base/config/interfaceConfigWhitelist.ts +++ b/react/features/base/config/interfaceConfigWhitelist.ts @@ -1,7 +1,7 @@ -import { inIframe } from '../util/iframeUtils'; +import { isEmbedded } from '../util/embedUtils'; import extraInterfaceConfigWhitelistCopy from './extraInterfaceConfigWhitelist'; -import inIframeInterfaceConfigWhitelist from './inIframeInterfaceConfigWhitelist'; +import isEmbeddedInterfaceConfigWhitelist from './isEmbeddedInterfaceConfigWhitelist'; /** * The interface config keys to whitelist, the keys that can be overridden. @@ -54,4 +54,4 @@ export default [ 'VERTICAL_FILMSTRIP', 'VIDEO_LAYOUT_FIT', 'VIDEO_QUALITY_LABEL_DISABLED' -].concat(extraInterfaceConfigWhitelistCopy).concat(inIframe() ? inIframeInterfaceConfigWhitelist : []); +].concat(extraInterfaceConfigWhitelistCopy).concat(isEmbedded() ? isEmbeddedInterfaceConfigWhitelist : []); diff --git a/react/features/base/config/isEmbeddedConfigWhitelist.ts b/react/features/base/config/isEmbeddedConfigWhitelist.ts new file mode 100644 index 0000000000..4398f1d9b8 --- /dev/null +++ b/react/features/base/config/isEmbeddedConfigWhitelist.ts @@ -0,0 +1,11 @@ +/** + * Additional config whitelist extending the original whitelist applied when Jitsi Meet is embedded + * in another app be that with an iframe or a mobile SDK. + */ +export default [ + 'customToolbarButtons', + 'defaultLogoUrl', + 'deploymentUrls', + 'liveStreaming', + 'salesforceUrl' +]; diff --git a/react/features/base/config/isEmbeddedInterfaceConfigWhitelist.ts b/react/features/base/config/isEmbeddedInterfaceConfigWhitelist.ts new file mode 100644 index 0000000000..fb35fb8203 --- /dev/null +++ b/react/features/base/config/isEmbeddedInterfaceConfigWhitelist.ts @@ -0,0 +1,6 @@ +/** + * Additional interface config whitelist extending the original whitelist applied when Jitsi Meet is embedded + * in another app be that with an iframe or a mobile SDK. + */ +export default [ +]; diff --git a/react/features/base/connection/actions.any.ts b/react/features/base/connection/actions.any.ts index 798de9cd5f..547993762f 100644 --- a/react/features/base/connection/actions.any.ts +++ b/react/features/base/connection/actions.any.ts @@ -5,7 +5,7 @@ import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/act import { getCurrentConference } from '../conference/functions'; import { IConfigState } from '../config/reducer'; import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet'; -import { inIframe } from '../util/iframeUtils'; +import { isEmbedded } from '../util/embedUtils'; import { parseURLParams } from '../util/parseURLParams'; import { appendURLParam, @@ -121,7 +121,7 @@ export function constructOptions(state: IReduxState) { const iceServersOverride = params['iceServers.replace']; // Allow iceServersOverride only when jitsi-meet is in an iframe. - if (inIframe() && iceServersOverride) { + if (isEmbedded() && iceServersOverride) { options.iceServersOverride = iceServersOverride; } diff --git a/react/features/base/jitsi-local-storage/setup.web.ts b/react/features/base/jitsi-local-storage/setup.web.ts index 292c7d5115..2ed1aaa86c 100644 --- a/react/features/base/jitsi-local-storage/setup.web.ts +++ b/react/features/base/jitsi-local-storage/setup.web.ts @@ -5,7 +5,7 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage'; import { safeJsonParse } from '@jitsi/js-utils/json'; import { browser } from '../lib-jitsi-meet'; -import { inIframe } from '../util/iframeUtils'; +import { isEmbedded } from '../util/embedUtils'; import { parseURLParams } from '../util/parseURLParams'; import logger from './logger'; @@ -41,7 +41,7 @@ function shouldUseHostPageLocalStorage(urlParams: { 'config.useHostPageLocalStor return true; } - if (browser.isWebKitBased() && inIframe()) { + if (browser.isWebKitBased() && isEmbedded()) { // WebKit browsers don't persist local storage for third-party iframes. return true; diff --git a/react/features/base/util/embedUtils.native.ts b/react/features/base/util/embedUtils.native.ts new file mode 100644 index 0000000000..23b6cb7c50 --- /dev/null +++ b/react/features/base/util/embedUtils.native.ts @@ -0,0 +1,32 @@ +import { getBundleId } from 'react-native-device-info'; + +/** + * BUndle ids for the Jitsi Meet apps. + */ +const JITSI_MEET_APPS = [ + + // iOS app. + 'com.atlassian.JitsiMeet.ios', + + // Android + iOS (testing) app. + 'org.jitsi.meet', + + // Android debug app. + 'org.jitsi.meet.debug', + + // 8x8 Work (Android). + 'org.vom8x8.sipua', + + // 8x8 Work (iOS). + 'com.yourcompany.Virtual-Office' +]; + +/** + * Checks whether we are loaded in iframe. In the mobile case we treat SDK + * consumers as the web treats iframes. + * + * @returns {boolean} Whether the current app is a Jitsi Meet app. + */ +export function isEmbedded(): boolean { + return !JITSI_MEET_APPS.includes(getBundleId()); +} diff --git a/react/features/base/util/iframeUtils.ts b/react/features/base/util/embedUtils.web.ts similarity index 66% rename from react/features/base/util/iframeUtils.ts rename to react/features/base/util/embedUtils.web.ts index 943c631164..9e267b872d 100644 --- a/react/features/base/util/iframeUtils.ts +++ b/react/features/base/util/embedUtils.web.ts @@ -3,11 +3,7 @@ * * @returns {boolean} Whether the current page is loaded in an iframe. */ -export function inIframe(): boolean { - if (navigator.product === 'ReactNative') { - return false; - } - +export function isEmbedded(): boolean { try { return window.self !== window.top; } catch (e) { diff --git a/react/features/conference/middleware.any.ts b/react/features/conference/middleware.any.ts index adbd900d06..3e6cf678b2 100644 --- a/react/features/conference/middleware.any.ts +++ b/react/features/conference/middleware.any.ts @@ -27,7 +27,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry'; import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes'; import { LOWER_HAND_MESSAGE } from '../base/tracks/constants'; import { BUTTON_TYPES } from '../base/ui/constants.any'; -import { inIframe } from '../base/util/iframeUtils'; +import { isEmbedded } from '../base/util/embedUtils'; import { isCalendarEnabled } from '../calendar-sync/functions'; import FeedbackDialog from '../feedback/components/FeedbackDialog'; import { setFilmstripEnabled } from '../filmstrip/actions.any'; @@ -191,8 +191,9 @@ function _checkIframe(state: IReduxState, dispatch: IStore['dispatch']) { } } - if (inIframe() && state['features/base/config'].disableIframeAPI && !browser.isElectron() - && !isVpaasMeeting(state) && !allowIframe) { + // TODO: enable for mobile too? + if (isEmbedded() && state['features/base/config'].disableIframeAPI && !browser.isElectron() + && !browser.isReactNative() && !isVpaasMeeting(state) && !allowIframe) { // show sticky notification and redirect in 5 minutes const { locationURL } = state['features/base/connection']; let translationKey = 'notify.disabledIframe'; diff --git a/react/features/recent-list/middleware.ts b/react/features/recent-list/middleware.ts index c431557cc1..dad164fe65 100644 --- a/react/features/recent-list/middleware.ts +++ b/react/features/recent-list/middleware.ts @@ -6,7 +6,7 @@ import { CONFERENCE_WILL_LEAVE, SET_ROOM } from '../base/conference/actionTypes' import { JITSI_CONFERENCE_URL_KEY } from '../base/conference/constants'; import { addKnownDomains } from '../base/known-domains/actions'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; -import { inIframe } from '../base/util/iframeUtils'; +import { isEmbedded } from '../base/util/embedUtils'; import { parseURIString } from '../base/util/uri'; import { _storeCurrentConference, _updateConferenceDuration } from './actions'; @@ -86,7 +86,7 @@ function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, ac const state = getState(); const { doNotStoreRoom } = state['features/base/config']; - if (!doNotStoreRoom && !inIframe()) { + if (!doNotStoreRoom && !isEmbedded()) { let locationURL; /** @@ -130,7 +130,7 @@ function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, ac function _setRoom({ dispatch, getState }: IStore, next: Function, action: AnyAction) { const { doNotStoreRoom } = getState()['features/base/config']; - if (!doNotStoreRoom && !inIframe() && action.room) { + if (!doNotStoreRoom && !isEmbedded() && action.room) { const { locationURL } = getState()['features/base/connection']; if (locationURL) { diff --git a/react/features/recording/components/Recording/LocalRecordingManager.web.ts b/react/features/recording/components/Recording/LocalRecordingManager.web.ts index a9f5610d4d..fcd033d434 100644 --- a/react/features/recording/components/Recording/LocalRecordingManager.web.ts +++ b/react/features/recording/components/Recording/LocalRecordingManager.web.ts @@ -6,7 +6,7 @@ import { IStore } from '../../../app/types'; import { getRoomName } from '../../../base/conference/functions'; import { MEDIA_TYPE } from '../../../base/media/constants'; import { getLocalTrack, getTrackState } from '../../../base/tracks/functions'; -import { inIframe } from '../../../base/util/iframeUtils'; +import { isEmbedded } from '../../../base/util/embedUtils'; import { stopLocalVideoRecording } from '../../actions.any'; interface ISelfRecording { @@ -180,7 +180,7 @@ const LocalRecordingManager: ILocalRecordingManager = { const { dispatch, getState } = store; // @ts-ignore - const supportsCaptureHandle = Boolean(navigator.mediaDevices.setCaptureHandleConfig) && !inIframe(); + const supportsCaptureHandle = Boolean(navigator.mediaDevices.setCaptureHandleConfig) && !isEmbedded(); const tabId = uuidV4(); this.selfRecording.on = onlySelf;