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.
This commit is contained in:
Saúl Ibarra Corretgé
2025-03-07 13:48:16 +01:00
committed by Saúl Ibarra Corretgé
parent 65e7fa571a
commit 1e8cc9d085
19 changed files with 81 additions and 43 deletions

View File

@@ -36,7 +36,7 @@
Component: JitsiMeetJS.app.entryPoints.APP Component: JitsiMeetJS.app.entryPoints.APP
}) })
const inIframe = () => { const isEmbedded = () => {
try { try {
return window.self !== window.top; return window.self !== window.top;
} catch (e) { } catch (e) {
@@ -45,7 +45,7 @@
}; };
const isElectron = navigator.userAgent.includes('Electron'); const isElectron = navigator.userAgent.includes('Electron');
const shouldRegisterWorker = !isElectron && !inIframe() && 'serviceWorker' in navigator; const shouldRegisterWorker = !isElectron && !isEmbedded() && 'serviceWorker' in navigator;
if (shouldRegisterWorker) { if (shouldRegisterWorker) {
navigator.serviceWorker navigator.serviceWorker

View File

@@ -12,8 +12,8 @@ import JitsiMeetJS, {
browser browser
} from '../base/lib-jitsi-meet'; } from '../base/lib-jitsi-meet';
import { isAnalyticsEnabled } from '../base/lib-jitsi-meet/functions.any'; import { isAnalyticsEnabled } from '../base/lib-jitsi-meet/functions.any';
import { isEmbedded } from '../base/util/embedUtils';
import { getJitsiMeetGlobalNS } from '../base/util/helpers'; import { getJitsiMeetGlobalNS } from '../base/util/helpers';
import { inIframe } from '../base/util/iframeUtils';
import { loadScript } from '../base/util/loadScript'; import { loadScript } from '../base/util/loadScript';
import { parseURLParams } from '../base/util/parseURLParams'; import { parseURLParams } from '../base/util/parseURLParams';
import { parseURIString } from '../base/util/uri'; import { parseURIString } from '../base/util/uri';
@@ -213,7 +213,7 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
permanentProperties.externalApi = typeof API_ID === 'number'; permanentProperties.externalApi = typeof API_ID === 'number';
// Report if we are loaded in iframe // Report if we are loaded in iframe
permanentProperties.inIframe = inIframe(); permanentProperties.inIframe = isEmbedded();
// Report the tenant from the URL. // Report the tenant from the URL.
permanentProperties.tenant = tenant || '/'; permanentProperties.tenant = tenant || '/';

View File

@@ -7,7 +7,7 @@ import {
} from '../base/config/actions'; } from '../base/config/actions';
import { setLocationURL } from '../base/connection/actions.web'; import { setLocationURL } from '../base/connection/actions.web';
import { loadConfig } from '../base/lib-jitsi-meet/functions.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 { parseURIString } from '../base/util/uri';
import { isVpaasMeeting } from '../jaas/functions'; import { isVpaasMeeting } from '../jaas/functions';
import { clearNotifications, showNotification } from '../notifications/actions'; 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 close page is enabled redirect to it, without further action
if (enableClosePage) { if (enableClosePage) {
if (isVpaasMeeting(getState())) { if (isVpaasMeeting(getState())) {
const isOpenedInIframe = inIframe(); const isOpenedInIframe = isEmbedded();
if (isOpenedInIframe) { if (isOpenedInIframe) {
// @ts-ignore // @ts-ignore

View File

@@ -12,6 +12,7 @@ import { clientResized, setSafeAreaInsets } from '../../base/responsive-ui/actio
import DimensionsDetector from '../../base/responsive-ui/components/DimensionsDetector.native'; import DimensionsDetector from '../../base/responsive-ui/components/DimensionsDetector.native';
import { updateSettings } from '../../base/settings/actions'; import { updateSettings } from '../../base/settings/actions';
import JitsiThemePaperProvider from '../../base/ui/components/JitsiThemeProvider.native'; import JitsiThemePaperProvider from '../../base/ui/components/JitsiThemeProvider.native';
import { isEmbedded } from '../../base/util/embedUtils.native';
import { _getRouteToRender } from '../getRouteToRender.native'; import { _getRouteToRender } from '../getRouteToRender.native';
import logger from '../logger'; import logger from '../logger';
@@ -87,7 +88,7 @@ export class App extends AbstractApp<IProps> {
const liteTxt = AppInfo.isLiteSDK ? ' (lite)' : ''; const liteTxt = AppInfo.isLiteSDK ? ' (lite)' : '';
logger.info(`Loaded SDK ${AppInfo.sdkVersion}${liteTxt}`); logger.info(`Loaded SDK ${AppInfo.sdkVersion}${liteTxt} isEmbedded=${isEmbedded()}`);
} }
/** /**

View File

@@ -7,7 +7,7 @@ import { SET_ROOM } from '../base/conference/actionTypes';
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes'; import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
import { getURLWithoutParams } from '../base/connection/utils'; import { getURLWithoutParams } from '../base/connection/utils';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { inIframe } from '../base/util/iframeUtils'; import { isEmbedded } from '../base/util/embedUtils';
import { reloadNow } from './actions'; import { reloadNow } from './actions';
import { _getRouteToRender } from './getRouteToRender'; import { _getRouteToRender } from './getRouteToRender';
@@ -52,7 +52,7 @@ function _connectionEstablished(store: IStore, next: Function, action: AnyAction
// @ts-ignore // @ts-ignore
const { history, location } = window; const { history, location } = window;
if (inIframe()) { if (isEmbedded()) {
return; return;
} }

View File

@@ -1,7 +1,7 @@
import { AnyAction } from 'redux'; import { AnyAction } from 'redux';
import MiddlewareRegistry from '../redux/MiddlewareRegistry'; 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 { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from './actionTypes';
import logger from './logger'; import logger from './logger';
@@ -24,7 +24,7 @@ MiddlewareRegistry.register(() => (next: Function) => (action: AnyAction) => {
case APP_WILL_MOUNT: { case APP_WILL_MOUNT: {
// Disable it inside an iframe until Google fixes the origin trial for 3rd party sources: // 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 // https://bugs.chromium.org/p/chromium/issues/detail?id=1504167
if (!inIframe() && 'PressureObserver' in globalThis) { if (!isEmbedded() && 'PressureObserver' in globalThis) {
pressureObserver = new window.PressureObserver( pressureObserver = new window.PressureObserver(
(records: typeof window.PressureRecord) => { (records: typeof window.PressureRecord) => {
logger.info('Compute pressure state changed:', JSON.stringify(records)); logger.info('Compute pressure state changed:', JSON.stringify(records));

View File

@@ -1,7 +1,7 @@
import { inIframe } from '../util/iframeUtils'; import { isEmbedded } from '../util/embedUtils';
import extraConfigWhitelist from './extraConfigWhitelist'; import extraConfigWhitelist from './extraConfigWhitelist';
import inIframeConfigWhitelist from './inIframeConfigWhitelist'; import isEmbeddedConfigWhitelist from './isEmbeddedConfigWhitelist';
/** /**
* The config keys to whitelist, the keys that can be overridden. * The config keys to whitelist, the keys that can be overridden.
@@ -249,4 +249,4 @@ export default [
'webrtcIceTcpDisable', 'webrtcIceTcpDisable',
'webrtcIceUdpDisable', 'webrtcIceUdpDisable',
'whiteboard.enabled' 'whiteboard.enabled'
].concat(extraConfigWhitelist).concat(inIframe() ? inIframeConfigWhitelist : []); ].concat(extraConfigWhitelist).concat(isEmbedded() ? isEmbeddedConfigWhitelist : []);

View File

@@ -1,4 +0,0 @@
/**
* Additional config whitelist extending the original whitelist in the case where jitsi-meet is loaded in an iframe.
*/
export default [];

View File

@@ -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 [];

View File

@@ -1,7 +1,7 @@
import { inIframe } from '../util/iframeUtils'; import { isEmbedded } from '../util/embedUtils';
import extraInterfaceConfigWhitelistCopy from './extraInterfaceConfigWhitelist'; import extraInterfaceConfigWhitelistCopy from './extraInterfaceConfigWhitelist';
import inIframeInterfaceConfigWhitelist from './inIframeInterfaceConfigWhitelist'; import isEmbeddedInterfaceConfigWhitelist from './isEmbeddedInterfaceConfigWhitelist';
/** /**
* The interface config keys to whitelist, the keys that can be overridden. * The interface config keys to whitelist, the keys that can be overridden.
@@ -54,4 +54,4 @@ export default [
'VERTICAL_FILMSTRIP', 'VERTICAL_FILMSTRIP',
'VIDEO_LAYOUT_FIT', 'VIDEO_LAYOUT_FIT',
'VIDEO_QUALITY_LABEL_DISABLED' 'VIDEO_QUALITY_LABEL_DISABLED'
].concat(extraInterfaceConfigWhitelistCopy).concat(inIframe() ? inIframeInterfaceConfigWhitelist : []); ].concat(extraInterfaceConfigWhitelistCopy).concat(isEmbedded() ? isEmbeddedInterfaceConfigWhitelist : []);

View File

@@ -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'
];

View File

@@ -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 [
];

View File

@@ -5,7 +5,7 @@ import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/act
import { getCurrentConference } from '../conference/functions'; import { getCurrentConference } from '../conference/functions';
import { IConfigState } from '../config/reducer'; import { IConfigState } from '../config/reducer';
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet'; import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
import { inIframe } from '../util/iframeUtils'; import { isEmbedded } from '../util/embedUtils';
import { parseURLParams } from '../util/parseURLParams'; import { parseURLParams } from '../util/parseURLParams';
import { import {
appendURLParam, appendURLParam,
@@ -121,7 +121,7 @@ export function constructOptions(state: IReduxState) {
const iceServersOverride = params['iceServers.replace']; const iceServersOverride = params['iceServers.replace'];
// Allow iceServersOverride only when jitsi-meet is in an iframe. // Allow iceServersOverride only when jitsi-meet is in an iframe.
if (inIframe() && iceServersOverride) { if (isEmbedded() && iceServersOverride) {
options.iceServersOverride = iceServersOverride; options.iceServersOverride = iceServersOverride;
} }

View File

@@ -5,7 +5,7 @@ import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
import { safeJsonParse } from '@jitsi/js-utils/json'; import { safeJsonParse } from '@jitsi/js-utils/json';
import { browser } from '../lib-jitsi-meet'; import { browser } from '../lib-jitsi-meet';
import { inIframe } from '../util/iframeUtils'; import { isEmbedded } from '../util/embedUtils';
import { parseURLParams } from '../util/parseURLParams'; import { parseURLParams } from '../util/parseURLParams';
import logger from './logger'; import logger from './logger';
@@ -41,7 +41,7 @@ function shouldUseHostPageLocalStorage(urlParams: { 'config.useHostPageLocalStor
return true; return true;
} }
if (browser.isWebKitBased() && inIframe()) { if (browser.isWebKitBased() && isEmbedded()) {
// WebKit browsers don't persist local storage for third-party iframes. // WebKit browsers don't persist local storage for third-party iframes.
return true; return true;

View File

@@ -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());
}

View File

@@ -3,11 +3,7 @@
* *
* @returns {boolean} Whether the current page is loaded in an iframe. * @returns {boolean} Whether the current page is loaded in an iframe.
*/ */
export function inIframe(): boolean { export function isEmbedded(): boolean {
if (navigator.product === 'ReactNative') {
return false;
}
try { try {
return window.self !== window.top; return window.self !== window.top;
} catch (e) { } catch (e) {

View File

@@ -27,7 +27,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes'; import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
import { LOWER_HAND_MESSAGE } from '../base/tracks/constants'; import { LOWER_HAND_MESSAGE } from '../base/tracks/constants';
import { BUTTON_TYPES } from '../base/ui/constants.any'; 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 { isCalendarEnabled } from '../calendar-sync/functions';
import FeedbackDialog from '../feedback/components/FeedbackDialog'; import FeedbackDialog from '../feedback/components/FeedbackDialog';
import { setFilmstripEnabled } from '../filmstrip/actions.any'; 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() // TODO: enable for mobile too?
&& !isVpaasMeeting(state) && !allowIframe) { if (isEmbedded() && state['features/base/config'].disableIframeAPI && !browser.isElectron()
&& !browser.isReactNative() && !isVpaasMeeting(state) && !allowIframe) {
// show sticky notification and redirect in 5 minutes // show sticky notification and redirect in 5 minutes
const { locationURL } = state['features/base/connection']; const { locationURL } = state['features/base/connection'];
let translationKey = 'notify.disabledIframe'; let translationKey = 'notify.disabledIframe';

View File

@@ -6,7 +6,7 @@ import { CONFERENCE_WILL_LEAVE, SET_ROOM } from '../base/conference/actionTypes'
import { JITSI_CONFERENCE_URL_KEY } from '../base/conference/constants'; import { JITSI_CONFERENCE_URL_KEY } from '../base/conference/constants';
import { addKnownDomains } from '../base/known-domains/actions'; import { addKnownDomains } from '../base/known-domains/actions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; 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 { parseURIString } from '../base/util/uri';
import { _storeCurrentConference, _updateConferenceDuration } from './actions'; import { _storeCurrentConference, _updateConferenceDuration } from './actions';
@@ -86,7 +86,7 @@ function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, ac
const state = getState(); const state = getState();
const { doNotStoreRoom } = state['features/base/config']; const { doNotStoreRoom } = state['features/base/config'];
if (!doNotStoreRoom && !inIframe()) { if (!doNotStoreRoom && !isEmbedded()) {
let locationURL; let locationURL;
/** /**
@@ -130,7 +130,7 @@ function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, ac
function _setRoom({ dispatch, getState }: IStore, next: Function, action: AnyAction) { function _setRoom({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
const { doNotStoreRoom } = getState()['features/base/config']; const { doNotStoreRoom } = getState()['features/base/config'];
if (!doNotStoreRoom && !inIframe() && action.room) { if (!doNotStoreRoom && !isEmbedded() && action.room) {
const { locationURL } = getState()['features/base/connection']; const { locationURL } = getState()['features/base/connection'];
if (locationURL) { if (locationURL) {

View File

@@ -6,7 +6,7 @@ import { IStore } from '../../../app/types';
import { getRoomName } from '../../../base/conference/functions'; import { getRoomName } from '../../../base/conference/functions';
import { MEDIA_TYPE } from '../../../base/media/constants'; import { MEDIA_TYPE } from '../../../base/media/constants';
import { getLocalTrack, getTrackState } from '../../../base/tracks/functions'; import { getLocalTrack, getTrackState } from '../../../base/tracks/functions';
import { inIframe } from '../../../base/util/iframeUtils'; import { isEmbedded } from '../../../base/util/embedUtils';
import { stopLocalVideoRecording } from '../../actions.any'; import { stopLocalVideoRecording } from '../../actions.any';
interface ISelfRecording { interface ISelfRecording {
@@ -180,7 +180,7 @@ const LocalRecordingManager: ILocalRecordingManager = {
const { dispatch, getState } = store; const { dispatch, getState } = store;
// @ts-ignore // @ts-ignore
const supportsCaptureHandle = Boolean(navigator.mediaDevices.setCaptureHandleConfig) && !inIframe(); const supportsCaptureHandle = Boolean(navigator.mediaDevices.setCaptureHandleConfig) && !isEmbedded();
const tabId = uuidV4(); const tabId = uuidV4();
this.selfRecording.on = onlySelf; this.selfRecording.on = onlySelf;