feat(deeplinking) Move deeplinking to config.js (#12704)

This commit is contained in:
Horatiu Muresan 2022-12-20 19:03:57 +02:00 committed by GitHub
parent 17b5009e63
commit 32dbdf2e5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 371 additions and 95 deletions

View File

@ -1091,10 +1091,67 @@ var config = {
// use only.
// _desktopSharingSourceDevice: 'sample-id-or-label',
// DEPRECATED! Use deeplinking.disabled instead.
// If true, any checks to handoff to another application will be prevented
// and instead the app will continue to display in the current browser.
// disableDeepLinking: false,
// The deeplinking config.
// For information about the properties of
// deeplinking.[ios/android].dynamicLink check:
// https://firebase.google.com/docs/dynamic-links/create-manually
// deeplinking: {
//
// // The desktop deeplinking config.
// desktop: {
// appName: 'Jitsi Meet'
// },
// // If true, any checks to handoff to another application will be prevented
// // and instead the app will continue to display in the current browser.
// disabled: false,
// // whether to hide the logo on the deep linking pages.
// hideLogo: false,
// // whether to show deeplinking image.
// showImage: false,
// // The ios deeplinking config.
// ios: {
// appName: 'Jitsi Meet',
// // Specify mobile app scheme for opening the app from the mobile browser.
// appScheme: 'org.jitsi.meet',
// // Custom URL for downloading ios mobile app.
// downloadLink: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
// dynamicLink: {
// apn: 'org.jitsi.meet',
// appCode: 'w2atb',
// customDomain: undefined,
// ibi: 'com.atlassian.JitsiMeet.ios',
// isi: '1165103905'
// }
// },
// // The android deeplinking config.
// android: {
// appName: 'Jitsi Meet',
// // Specify mobile app scheme for opening the app from the mobile browser.
// appScheme: 'org.jitsi.meet',
// // Custom URL for downloading android mobile app.
// downloadLink: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
// // Android app package name.
// appPackage: 'org.jitsi.meet',
// fDroidUrl: 'https://f-droid.org/en/packages/org.jitsi.meet/',
// dynamicLink: {
// apn: 'org.jitsi.meet',
// appCode: 'w2atb',
// customDomain: undefined,
// ibi: 'com.atlassian.JitsiMeet.ios',
// isi: '1165103905'
// }
// }
// },
// A property to disable the right click context menu for localVideo
// the menu has option to flip the locally seen video for local presentations
// disableLocalVideoFlip: false,

View File

@ -76,11 +76,6 @@ var interfaceConfig = {
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
/**
* Hide the logo on the deep linking pages.
*/
HIDE_DEEP_LINKING_LOGO: false,
/**
* Hide the invite prompt in the header when alone in the meeting.
*/
@ -108,23 +103,6 @@ var interfaceConfig = {
*/
MOBILE_APP_PROMO: true,
/**
* Specify custom URL for downloading android mobile app.
*/
MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
/**
* Specify custom URL for downloading f droid app.
*/
MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
/**
* Specify URL for downloading ios mobile app.
*/
MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
NATIVE_APP_NAME: 'Jitsi Meet',
// Names of browsers which should show a warning stating the current browser
// has a suboptimal experience. Browsers which are not listed as optimal or
// unsupported are considered suboptimal. Valid values are:
@ -159,7 +137,6 @@ var interfaceConfig = {
*/
SHOW_CHROME_EXTENSION_BANNER: false,
SHOW_DEEP_LINKING_IMAGE: false,
SHOW_JITSI_WATERMARK: true,
SHOW_POWERED_BY: false,
SHOW_PROMOTIONAL_CLOSE_PAGE: false,
@ -200,6 +177,33 @@ var interfaceConfig = {
*/
// TILE_VIEW_MAX_COLUMNS: 5,
// List of undocumented settings
/**
INDICATOR_FONT_SIZES
PHONE_NUMBER_REGEX
*/
// -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------
/**
* Specify URL for downloading ios mobile app.
*/
// MOBILE_DOWNLOAD_LINK_IOS: 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905',
/**
* Specify custom URL for downloading android mobile app.
*/
// MOBILE_DOWNLOAD_LINK_ANDROID: 'https://play.google.com/store/apps/details?id=org.jitsi.meet',
// SHOW_DEEP_LINKING_IMAGE: false,
/**
* Specify mobile app scheme for opening the app from the mobile browser.
*/
// APP_SCHEME: 'org.jitsi.meet',
// NATIVE_APP_NAME: 'Jitsi Meet',
/**
* Specify Firebase dynamic link properties for the mobile apps.
*/
@ -212,22 +216,19 @@ var interfaceConfig = {
// },
/**
* Specify mobile app scheme for opening the app from the mobile browser.
* Hide the logo on the deep linking pages.
*/
// APP_SCHEME: 'org.jitsi.meet',
// HIDE_DEEP_LINKING_LOGO: false,
/**
* Specify the Android app package name.
*/
// ANDROID_APP_PACKAGE: 'org.jitsi.meet',
// List of undocumented settings
/**
INDICATOR_FONT_SIZES
PHONE_NUMBER_REGEX
*/
// -----------------DEPRECATED CONFIGS BELOW THIS LINE-----------------------------
* Specify custom URL for downloading f droid app.
*/
// MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
// Connection indicators (
// CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,

View File

@ -88,6 +88,36 @@ export type Sounds = 'ASKED_TO_UNMUTE_SOUND' |
'RECORDING_ON_SOUND' |
'TALK_WHILE_MUTED_SOUND';
export interface IMobileDynamicLink {
apn: string;
appCode: string;
customDomain?: string;
ibi: string;
isi: string;
}
export interface IDeeplinkingPlatformConfig {
appName: string;
}
export interface IDeeplinkingMobileConfig extends IDeeplinkingPlatformConfig {
appPackage?: string;
appScheme: string;
downloadLink: string;
dynamicLink?: IMobileDynamicLink;
fDroidUrl?: string;
}
export interface IDeeplinkingConfig {
android?: IDeeplinkingMobileConfig;
desktop?: IDeeplinkingPlatformConfig;
disabled: boolean;
hideLogo: boolean;
ios?: IDeeplinkingMobileConfig;
showImage: boolean;
}
export interface IConfig {
_desktopSharingSourceDevice?: string;
analytics?: {
@ -176,6 +206,7 @@ export interface IConfig {
};
};
corsAvatarURLs?: Array<string>;
deeplinking?: IDeeplinkingConfig;
defaultLanguage?: string;
defaultLocalDisplayName?: string;
defaultLogoUrl?: string;

View File

@ -81,6 +81,8 @@ export default [
'brandingRoomAlias',
'debug',
'debugAudioLevels',
'deeplinking.disabled',
'deeplinking.showImage',
'defaultLocalDisplayName',
'defaultRemoteDisplayName',
'deploymentUrls',

View File

@ -4,7 +4,7 @@ import { IReduxState } from '../../app/types';
import { REPLACE_PARTICIPANT } from '../flags/constants';
import { getFeatureFlag } from '../flags/functions';
import { IConfig } from './configType';
import { IConfig, IDeeplinkingConfig } from './configType';
export * from './functions.any';
@ -42,3 +42,14 @@ export function _cleanupConfig(config: IConfig) {
export function getReplaceParticipant(state: IReduxState): string {
return getFeatureFlag(state, REPLACE_PARTICIPANT, false);
}
/**
* Sets the defaults for deeplinking.
*
* @param {IDeeplinkingConfig} _deeplinking - The deeplinking config.
* @returns {void}
*/
export function _setDeeplinkingDefaults(_deeplinking: IDeeplinkingConfig) {
return;
}

View File

@ -1,6 +1,6 @@
import { IReduxState } from '../../app/types';
import { IConfig } from './configType';
import { IConfig, IDeeplinkingConfig, IDeeplinkingMobileConfig, IDeeplinkingPlatformConfig } from './configType';
import { TOOLBAR_BUTTONS } from './constants';
export * from './functions.any';
@ -8,10 +8,11 @@ export * from './functions.any';
/**
* Removes all analytics related options from the given configuration, in case of a libre build.
*
* @param {*} config - The configuration which needs to be cleaned up.
* @param {*} _config - The configuration which needs to be cleaned up.
* @returns {void}
*/
export function _cleanupConfig(config: IConfig) { // eslint-disable-line @typescript-eslint/no-unused-vars
export function _cleanupConfig(_config: IConfig) {
return;
}
/**
@ -60,3 +61,43 @@ export function areAudioLevelsEnabled(state: IReduxState): boolean {
// Default to false for React Native as audio levels are of no interest to the mobile app.
return navigator.product !== 'ReactNative' && !state['features/base/config'].disableAudioLevels;
}
/**
* Sets the defaults for deeplinking.
*
* @param {IDeeplinkingConfig} deeplinking - The deeplinking config.
* @returns {void}
*/
export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
const {
desktop = {} as IDeeplinkingPlatformConfig,
android = {} as IDeeplinkingMobileConfig,
ios = {} as IDeeplinkingMobileConfig
} = deeplinking;
desktop.appName = desktop.appName || 'Jitsi Meet';
ios.appName = ios.appName || 'Jitsi Meet';
ios.appScheme = ios.appScheme || 'org.jitsi.meet';
ios.downloadLink = ios.downloadLink
|| 'https://itunes.apple.com/us/app/jitsi-meet/id1165103905';
if (ios.dynamicLink) {
ios.dynamicLink.apn = ios.dynamicLink.apn || 'org.jitsi.meet';
ios.dynamicLink.appCode = ios.dynamicLink.appCode || 'w2atb';
ios.dynamicLink.ibi = ios.dynamicLink.ibi || 'com.atlassian.JitsiMeet.ios';
ios.dynamicLink.isi = ios.dynamicLink.isi || '1165103905';
}
android.appName = android.appName || 'Jitsi Meet';
android.appScheme = android.appScheme || 'org.jitsi.meet';
android.downloadLink = android.downloadLink
|| 'https://play.google.com/store/apps/details?id=org.jitsi.meet';
android.appPackage = android.appPackage || 'org.jitsi.meet';
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/en/packages/org.jitsi.meet/';
if (android.dynamicLink) {
android.dynamicLink.apn = android.dynamicLink.apn || 'org.jitsi.meet';
android.dynamicLink.appCode = android.dynamicLink.appCode || 'w2atb';
android.dynamicLink.ibi = android.dynamicLink.ibi || 'com.atlassian.JitsiMeet.ios';
android.dynamicLink.isi = android.dynamicLink.isi || '1165103905';
}
}

View File

@ -11,8 +11,14 @@ import {
SET_CONFIG,
UPDATE_CONFIG
} from './actionTypes';
import { IConfig } from './configType';
import { _cleanupConfig } from './functions';
import {
IConfig,
IDeeplinkingConfig,
IDeeplinkingMobileConfig,
IDeeplinkingPlatformConfig,
IMobileDynamicLink
} from './configType';
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
/**
* The initial state of the feature base/config when executing in a
@ -292,6 +298,52 @@ function _translateInterfaceConfig(oldValue: IConfig) {
}
}
// if we have `deeplinking` defined, ignore deprecated values. Otherwise, compose the config.
if (!oldValue.deeplinking) {
const disabled = Boolean(oldValue.disableDeepLinking);
const deeplinking: IDeeplinkingConfig = {
desktop: {} as IDeeplinkingPlatformConfig,
hideLogo: false,
disabled,
showImage: false,
android: {} as IDeeplinkingMobileConfig,
ios: {} as IDeeplinkingMobileConfig
};
if (typeof interfaceConfig === 'object') {
const mobileDynamicLink = interfaceConfig.MOBILE_DYNAMIC_LINK;
const dynamicLink: IMobileDynamicLink | undefined = mobileDynamicLink ? {
apn: mobileDynamicLink.APN,
appCode: mobileDynamicLink.APP_CODE,
ibi: mobileDynamicLink.IBI,
isi: mobileDynamicLink.ISI,
customDomain: mobileDynamicLink.CUSTOM_DOMAIN
} : undefined;
if (deeplinking.desktop) {
deeplinking.desktop.appName = interfaceConfig.NATIVE_APP_NAME;
}
deeplinking.hideLogo = Boolean(interfaceConfig.HIDE_DEEP_LINKING_LOGO);
deeplinking.showImage = interfaceConfig.SHOW_DEEP_LINKING_IMAGE;
deeplinking.android = {
appName: interfaceConfig.NATIVE_APP_NAME,
appScheme: interfaceConfig.APP_SCHEME,
downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_ANDROID,
appPackage: interfaceConfig.ANDROID_APP_PACKAGE,
fDroidUrl: interfaceConfig.MOBILE_DOWNLOAD_LINK_F_DROID,
dynamicLink
};
deeplinking.ios = {
appName: interfaceConfig.NATIVE_APP_NAME,
appScheme: interfaceConfig.APP_SCHEME,
downloadLink: interfaceConfig.MOBILE_DOWNLOAD_LINK_IOS,
dynamicLink
};
}
newValue.deeplinking = deeplinking;
}
return newValue;
}
@ -480,6 +532,8 @@ function _translateLegacyConfig(oldValue: IConfig) {
};
}
_setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
return newValue;
}

View File

@ -5,6 +5,7 @@ import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
import { IDeeplinkingConfig } from '../../base/config/configType';
import { isSupportedBrowser } from '../../base/environment';
import { translate } from '../../base/i18n';
import { connect } from '../../base/redux';
@ -16,14 +17,17 @@ import {
} from '../actions';
import { _TNS } from '../constants';
declare var interfaceConfig: Object;
/**
* The type of the React {@code Component} props of
* {@link DeepLinkingDesktopPage}.
*/
type Props = {
/**
* The deeplinking config.
*/
_deeplinkingCfg: IDeeplinkingConfig,
/**
* Used to dispatch actions from the buttons.
*/
@ -72,10 +76,10 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
* @returns {ReactElement}
*/
render() {
const { t } = this.props;
const { HIDE_DEEP_LINKING_LOGO, NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
const { t, _deeplinkingCfg: { desktop = {}, hideLogo, showImage } } = this.props;
const { appName } = desktop;
const rightColumnStyle
= SHOW_DEEP_LINKING_IMAGE ? null : { width: '100%' };
= showImage ? null : { width: '100%' };
return (
@ -84,7 +88,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
<div className = 'deep-linking-desktop'>
<div className = 'header'>
{
HIDE_DEEP_LINKING_LOGO
hideLogo
? null
: <img
alt = { t('welcomepage.logo.logoDeepLinking') }
@ -94,7 +98,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
</div>
<div className = 'content'>
{
SHOW_DEEP_LINKING_IMAGE
showImage
? <div className = 'leftColumn'>
<div className = 'leftColumnContent'>
<div className = 'image' />
@ -108,7 +112,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
<h1 className = 'title'>
{
t(`${_TNS}.title`,
{ app: NATIVE_APP_NAME })
{ app: appName })
}
</h1>
<p className = 'description'>
@ -117,7 +121,7 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
`${_TNS}.${isSupportedBrowser()
? 'description'
: 'descriptionWithoutWeb'}`,
{ app: NATIVE_APP_NAME }
{ app: appName }
)
}
</p>
@ -171,4 +175,18 @@ class DeepLinkingDesktopPage<P : Props> extends Component<P> {
}
}
export default translate(connect()(DeepLinkingDesktopPage));
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code DeepLinkingDesktopPage} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
};
}
export default translate(connect(_mapStateToProps)(DeepLinkingDesktopPage));

View File

@ -4,6 +4,7 @@ import React, { Component } from 'react';
import type { Dispatch } from 'redux';
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
import { IDeeplinkingConfig, IDeeplinkingMobileConfig } from '../../base/config/configType';
import { isSupportedMobileBrowser } from '../../base/environment';
import { translate } from '../../base/i18n';
import { Platform } from '../../base/react';
@ -14,8 +15,6 @@ import { _TNS } from '../constants';
import { generateDeepLinkingURL } from '../functions';
import { renderPromotionalFooter } from '../renderPromotionalFooter';
declare var interfaceConfig: Object;
/**
* The namespace of the CSS styles of DeepLinkingMobilePage.
*
@ -31,9 +30,19 @@ const _SNS = 'deep-linking-mobile';
type Props = {
/**
* Application download URL.
* The deeplinking config.
*/
_downloadUrl: ?string,
_deeplinkingCfg: IDeeplinkingConfig,
/**
* Application mobile deeplinking config.
*/
_mobileConfig: IDeeplinkingMobileConfig,
/**
* The deeplinking url.
*/
_deepLinkingUrl: string,
/**
* The name of the conference attempting to being joined.
@ -95,13 +104,19 @@ class DeepLinkingMobilePage extends Component<Props> {
* @returns {ReactElement}
*/
render() {
const { _downloadUrl, _room, t, _url } = this.props;
const { HIDE_DEEP_LINKING_LOGO, NATIVE_APP_NAME, SHOW_DEEP_LINKING_IMAGE } = interfaceConfig;
const {
_deeplinkingCfg: { hideLogo, showImage },
_mobileConfig: { downloadLink, appName },
_room,
t,
_url,
_deepLinkingUrl
} = this.props;
const downloadButtonClassName
= `${_SNS}__button ${_SNS}__button_primary`;
const onOpenLinkProperties = _downloadUrl
const onOpenLinkProperties = downloadLink
? {
// When opening a link to the download page, we want to let the
// OS itself handle intercepting and opening the appropriate
@ -121,7 +136,7 @@ class DeepLinkingMobilePage extends Component<Props> {
<div className = { _SNS }>
<div className = 'header'>
{
HIDE_DEEP_LINKING_LOGO
hideLogo
? null
: <img
alt = { t('welcomepage.logo.logoDeepLinking') }
@ -131,7 +146,7 @@ class DeepLinkingMobilePage extends Component<Props> {
</div>
<div className = { `${_SNS}__body` }>
{
SHOW_DEEP_LINKING_IMAGE
showImage
? <img
alt = { t('welcomepage.logo.logoDeepLinking') }
className = 'image'
@ -139,7 +154,7 @@ class DeepLinkingMobilePage extends Component<Props> {
: null
}
<p className = { `${_SNS}__text` }>
{ t(`${_TNS}.appNotInstalled`, { app: NATIVE_APP_NAME }) }
{ t(`${_TNS}.appNotInstalled`, { app: appName }) }
</p>
<p className = { `${_SNS}__text` }>
{ t(`${_TNS}.ifHaveApp`) }
@ -147,7 +162,7 @@ class DeepLinkingMobilePage extends Component<Props> {
<a
{ ...onOpenLinkProperties }
className = { `${_SNS}__href` }
href = { generateDeepLinkingURL() }
href = { _deepLinkingUrl }
onClick = { this._onOpenApp }
target = '_top'>
<button className = { `${_SNS}__button ${_SNS}__button_primary` }>
@ -200,32 +215,28 @@ class DeepLinkingMobilePage extends Component<Props> {
* @returns {string} - The URL for downloading the app.
*/
_generateDownloadURL() {
const { _downloadUrl: url } = this.props;
const { _mobileConfig: { downloadLink, dynamicLink, appScheme } } = this.props;
if (url && typeof interfaceConfig.MOBILE_DYNAMIC_LINK === 'undefined') {
return url;
if (downloadLink && typeof dynamicLink === 'undefined') {
return downloadLink;
}
// For information about the properties of
// interfaceConfig.MOBILE_DYNAMIC_LINK check:
// https://firebase.google.com/docs/dynamic-links/create-manually
const {
APN = 'org.jitsi.meet',
APP_CODE = 'w2atb',
CUSTOM_DOMAIN = undefined,
IBI = 'com.atlassian.JitsiMeet.ios',
ISI = '1165103905'
} = interfaceConfig.MOBILE_DYNAMIC_LINK || {};
apn,
appCode,
customDomain,
ibi,
isi
} = dynamicLink || {};
const domain = CUSTOM_DOMAIN ?? `https://${APP_CODE}.app.goo.gl`;
const IUS = interfaceConfig.APP_SCHEME || 'org.jitsi.meet';
const domain = customDomain ?? `https://${appCode}.app.goo.gl`;
return `${domain}/?link=${
encodeURIComponent(window.location.href)}&apn=${
APN}&ibi=${
IBI}&isi=${
ISI}&ius=${
IUS}&efr=1`;
apn}&ibi=${
ibi}&isi=${
isi}&ius=${
appScheme}&efr=1`;
}
_onDownloadApp: () => void;
@ -281,11 +292,15 @@ class DeepLinkingMobilePage extends Component<Props> {
*/
function _mapStateToProps(state) {
const { locationURL = {} } = state['features/base/connection'];
const { deeplinking } = state['features/base/config'];
const mobileConfig = deeplinking?.[Platform.OS] || {};
return {
_downloadUrl: interfaceConfig[`MOBILE_DOWNLOAD_LINK_${Platform.OS.toUpperCase()}`],
_deeplinkingCfg: deeplinking || {},
_mobileConfig: mobileConfig,
_room: decodeURIComponent(state['features/base/conference'].room),
_url: locationURL
_url: locationURL,
_deepLinkingUrl: generateDeepLinkingURL(state)
};
}

View File

@ -3,15 +3,28 @@
import React, { Component } from 'react';
import { createDeepLinkingPageEvent, sendAnalytics } from '../../analytics';
import { IDeeplinkingConfig } from '../../base/config/configType';
import { connect } from '../../base/redux';
declare var interfaceConfig: Object;
/**
* The type of the React {@code Component} props of
* {@link NoMobileApp}.
*/
type Props = {
/**
* The deeplinking config.
*/
_deeplinkingCfg: IDeeplinkingConfig,
};
/**
* React component representing no mobile app page.
*
* @class NoMobileApp
*/
export default class NoMobileApp extends Component<*> {
class NoMobileApp<P : Props> extends Component<P> {
/**
* Implements the Component's componentDidMount method.
*
@ -30,6 +43,7 @@ export default class NoMobileApp extends Component<*> {
*/
render() {
const ns = 'no-mobile-app';
const { desktop: { appName } } = this.props._deeplinkingCfg;
return (
<div className = { ns }>
@ -37,10 +51,26 @@ export default class NoMobileApp extends Component<*> {
Video chat isn't available on mobile.
</h2>
<p className = { `${ns}__description` }>
Please use { interfaceConfig.NATIVE_APP_NAME } on desktop to
Please use { appName } on desktop to
join calls.
</p>
</div>
);
}
}
/**
* Maps (parts of) the Redux state to the associated props for the
* {@code NoMobileApp} component.
*
* @param {Object} state - The Redux state.
* @private
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
_deeplinkingCfg: state['features/base/config'].deeplinking || {}
};
}
export default connect(_mapStateToProps)(NoMobileApp);

View File

@ -15,27 +15,31 @@ import { _openDesktopApp } from './openDesktopApp';
/**
* Generates a deep linking URL based on the current window URL.
*
* @param {Object} state - Object containing current redux state.
*
* @returns {string} - The generated URL.
*/
export function generateDeepLinkingURL() {
export function generateDeepLinkingURL(state) {
// If the user installed the app while this Component was displayed
// (e.g. the user clicked the Download the App button), then we would
// like to open the current URL in the mobile app. The only way to do it
// appears to be a link with an app-specific scheme, not a Universal
// Link.
const appScheme = interfaceConfig.APP_SCHEME || 'org.jitsi.meet';
const { href } = window.location;
const regex = new RegExp(URI_PROTOCOL_PATTERN, 'gi');
const mobileConfig = state['features/base/config'].deeplinking?.[Platform.OS] || {};
const { appScheme, appPackage } = mobileConfig;
// Android: use an intent link, custom schemes don't work in all browsers.
// https://developer.chrome.com/multidevice/android/intents
if (Platform.OS === 'android') {
// https://meet.jit.si/foo -> meet.jit.si/foo
const url = href.replace(regex, '').substr(2);
const pkg = interfaceConfig.ANDROID_APP_PACKAGE || 'org.jitsi.meet';
return `intent://${url}#Intent;scheme=${appScheme};package=${pkg};end`;
return `intent://${url}#Intent;scheme=${appScheme};package=${appPackage};end`;
}
// iOS: Replace the protocol part with the app scheme.
@ -52,12 +56,13 @@ export function generateDeepLinkingURL() {
export function getDeepLinkingPage(state) {
const { room } = state['features/base/conference'];
const { launchInWeb } = state['features/deep-linking'];
const appScheme = typeof interfaceConfig !== 'undefined' && interfaceConfig.APP_SCHEME;
const deeplinking = state['features/base/config'].deeplinking || {};
const { appScheme } = deeplinking?.[Platform.OS] || {};
// Show only if we are about to join a conference.
if (launchInWeb
|| !room
|| state['features/base/config'].disableDeepLinking
|| state['features/base/config'].deeplinking?.disabled
|| (isVpaasMeeting(state) && (!appScheme || appScheme === 'com.8x8.meet'))) {
return Promise.resolve();
}

View File

@ -6,6 +6,7 @@ import type { Dispatch } from 'redux';
import { createWelcomePageEvent, sendAnalytics } from '../../analytics';
import { appNavigate } from '../../app/actions';
import { IDeeplinkingConfig } from '../../base/config/configType';
import isInsecureRoomName from '../../base/util/isInsecureRoomName';
import { isCalendarEnabled } from '../../calendar-sync';
import { isRecentListEnabled } from '../../recent-list/functions';
@ -20,6 +21,11 @@ export type Props = {
*/
_calendarEnabled: boolean,
/**
* The deeplinking config.
*/
_deeplinkingCfg: IDeeplinkingConfig,
/**
* Whether the insecure room name functionality is enabled or not.
*/
@ -268,6 +274,7 @@ export class AbstractWelcomePage<P: Props> extends Component<P, *> {
export function _mapStateToProps(state: Object) {
return {
_calendarEnabled: isCalendarEnabled(state),
_deeplinkingCfg: state['features/base/config'].deeplinking,
_enableInsecureRoomNameWarning: state['features/base/config'].enableInsecureRoomNameWarning || false,
_moderatedRoomServiceUrl: state['features/base/config'].moderatedRoomServiceUrl,
_recentListEnabled: isRecentListEnabled(),

View File

@ -343,12 +343,16 @@ class WelcomePage extends AbstractWelcomePage {
* @returns {ReactElement}
*/
_renderFooter() {
const { t } = this.props;
const {
MOBILE_DOWNLOAD_LINK_ANDROID,
MOBILE_DOWNLOAD_LINK_F_DROID,
MOBILE_DOWNLOAD_LINK_IOS
} = interfaceConfig;
t,
_deeplinkingCfg: {
ios: { downloadLink: iosDownloadLink },
android: {
fDroidUrl,
downloadLink: androidDownloadLink
}
}
} = this.props;
return (<footer className = 'welcome-footer'>
<div className = 'welcome-footer-centered'>
@ -357,21 +361,21 @@ class WelcomePage extends AbstractWelcomePage {
<div className = 'welcome-footer-row-1-text'>{t('welcomepage.jitsiOnMobile')}</div>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_IOS }>
href = { iosDownloadLink }>
<img
alt = { t('welcomepage.mobileDownLoadLinkIos') }
src = './images/app-store-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_ANDROID }>
href = { androidDownloadLink }>
<img
alt = { t('welcomepage.mobileDownLoadLinkAndroid') }
src = './images/google-play-badge.png' />
</a>
<a
className = 'welcome-badge'
href = { MOBILE_DOWNLOAD_LINK_F_DROID }>
href = { fDroidUrl }>
<img
alt = { t('welcomepage.mobileDownLoadLinkFDroid') }
src = './images/f-droid-badge.png' />