diff --git a/Makefile b/Makefile index 4cbbdfea7..998b2fc19 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ deploy-rnnoise-binary: deploy-css: $(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \ - $(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \ + $(CLEANCSS) --skip-rebase $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \ rm $(STYLES_BUNDLE) deploy-local: diff --git a/SECURITY.md b/SECURITY.md index d8441922d..58d25b8ac 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,9 @@ # Security -## Reporting security issuess +## Reporting security issues We take security very seriously and develop all Jitsi projects to be secure and safe. -If you find (or simply suspect) a security issue in any of the Jitsi projects, please send us an email to security@jitsi.org. +If you find (or simply suspect) a security issue in any of the Jitsi projects, please report it to us via [HackerOne](https://hackerone.com/8x8) or send us an email to security@jitsi.org. **We encourage responsible disclosure for the sake of our users, so please reach out before posting in a public space.** diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8d805f038..f5085e24d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + package="org.jitsi.meet" + android:installLocation="auto"> = Build.VERSION_CODES.M - && getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M; - } } diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml index 59a524cdc..cfdf6a502 100644 --- a/android/app/src/main/res/xml/network_security_config.xml +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -1,6 +1,12 @@ - - - localhost - 10.0.2.2 - + + + + + + + + + localhost + 10.0.2.2 + diff --git a/android/build.gradle b/android/build.gradle index 60758af5c..755d99612 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -142,10 +142,10 @@ allprojects { } ext { - buildToolsVersion = "28.0.3" - compileSdkVersion = 28 - minSdkVersion = 21 - targetSdkVersion = 28 + buildToolsVersion = "29.0.3" + compileSdkVersion = 29 + minSdkVersion = 23 + targetSdkVersion = 29 supportLibVersion = "28.0.0" // The Maven artifact groupdId of the third-party react-native modules which diff --git a/android/gradle.properties b/android/gradle.properties index 9df2468f5..d9dad0546 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -20,5 +20,5 @@ android.useAndroidX=true android.enableJetifier=true -appVersion=20.3.0 -sdkVersion=2.9.0 +appVersion=20.4.0 +sdkVersion=2.10.0 diff --git a/android/scripts/run-packager-helper.command b/android/scripts/run-packager-helper.command new file mode 100755 index 000000000..68f9867e5 --- /dev/null +++ b/android/scripts/run-packager-helper.command @@ -0,0 +1,5 @@ +#!/bin/bash + +THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd) + +exec ${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command --reset-cache diff --git a/android/scripts/run-packager.sh b/android/scripts/run-packager.sh index 6b112628c..f7a5bcedb 100755 --- a/android/scripts/run-packager.sh +++ b/android/scripts/run-packager.sh @@ -8,7 +8,7 @@ THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOUR export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}" echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${THIS_DIR}/../../node_modules/react-native/scripts/.packager.env" -adb reverse tcp:8081 tcp:8081 +adb reverse tcp:$RCT_METRO_PORT tcp:$RCT_METRO_PORT if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then @@ -16,11 +16,10 @@ if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then exit 2 fi else - CMD="${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command" + CMD="$THIS_DIR/run-packager-helper.command" if [[ `uname` == "Darwin" ]]; then open -g "${CMD}" || echo "Can't start packager automatically" else xdg-open "${CMD}" || echo "Can't start packager automatically" fi fi - diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerGeneric.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerGeneric.java index b6e57342c..1b8ab1622 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerGeneric.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerGeneric.java @@ -16,11 +16,8 @@ package org.jitsi.meet.sdk; -import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioManager; -import android.os.Build; -import androidx.annotation.RequiresApi; import java.util.HashSet; import java.util.Set; @@ -34,7 +31,6 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger; * default it's only used on versions < O, since versions >= O use ConnectionService, but it * can be disabled. */ -@RequiresApi(Build.VERSION_CODES.M) class AudioDeviceHandlerGeneric implements AudioModeModule.AudioDeviceHandlerInterface, AudioManager.OnAudioFocusChangeListener { diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerLegacy.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerLegacy.java deleted file mode 100644 index db43bed77..000000000 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioDeviceHandlerLegacy.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright @ 2017-present 8x8, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jitsi.meet.sdk; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.media.AudioManager; - -import org.jitsi.meet.sdk.log.JitsiMeetLogger; - - -/** - * {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for - * legacy (pre-M) Android versions. - */ -class AudioDeviceHandlerLegacy implements - AudioModeModule.AudioDeviceHandlerInterface, - AudioManager.OnAudioFocusChangeListener, - BluetoothHeadsetMonitor.Listener { - - private final static String TAG = AudioDeviceHandlerLegacy.class.getSimpleName(); - - /** - * Reference to the main {@code AudioModeModule}. - */ - private AudioModeModule module; - - /** - * Indicator that we have lost audio focus. - */ - private boolean audioFocusLost = false; - - /** - * {@link AudioManager} instance used to interact with the Android audio - * subsystem. - */ - private AudioManager audioManager; - - /** - * {@link BluetoothHeadsetMonitor} for detecting Bluetooth device changes in - * old (< M) Android versions. - */ - private BluetoothHeadsetMonitor bluetoothHeadsetMonitor; - - public AudioDeviceHandlerLegacy(AudioManager audioManager) { - this.audioManager = audioManager; - } - - /** - * Helper method to trigger an audio route update when Bluetooth devices are - * connected / disconnected. - */ - @Override - public void onBluetoothDeviceChange(final boolean deviceAvailable) { - module.runInAudioThread(new Runnable() { - @Override - public void run() { - if (deviceAvailable) { - module.addDevice(AudioModeModule.DEVICE_BLUETOOTH); - } else { - module.removeDevice(AudioModeModule.DEVICE_BLUETOOTH); - } - - module.updateAudioRoute(); - } - }); - } - - /** - * Helper method to trigger an audio route update when a headset is plugged - * or unplugged. - */ - private void onHeadsetDeviceChange() { - module.runInAudioThread(new Runnable() { - @Override - public void run() { - // XXX: isWiredHeadsetOn is not deprecated when used just for - // knowing if there is a wired headset connected, regardless of - // audio being routed to it. - //noinspection deprecation - if (audioManager.isWiredHeadsetOn()) { - module.addDevice(AudioModeModule.DEVICE_HEADPHONES); - } else { - module.removeDevice(AudioModeModule.DEVICE_HEADPHONES); - } - - module.updateAudioRoute(); - } - }); - } - - /** - * {@link AudioManager.OnAudioFocusChangeListener} interface method. Called - * when the audio focus of the system is updated. - * - * @param focusChange - The type of focus change. - */ - @Override - public void onAudioFocusChange(final int focusChange) { - module.runInAudioThread(new Runnable() { - @Override - public void run() { - switch (focusChange) { - case AudioManager.AUDIOFOCUS_GAIN: { - JitsiMeetLogger.d(TAG + " Audio focus gained"); - // Some other application potentially stole our audio focus - // temporarily. Restore our mode. - if (audioFocusLost) { - module.updateAudioRoute(); - } - audioFocusLost = false; - break; - } - case AudioManager.AUDIOFOCUS_LOSS: - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: - case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: { - JitsiMeetLogger.d(TAG + " Audio focus lost"); - audioFocusLost = true; - break; - } - } - } - }); - } - - /** - * Helper method to set the output route to a Bluetooth device. - * - * @param enabled true if Bluetooth should use used, false otherwise. - */ - private void setBluetoothAudioRoute(boolean enabled) { - if (enabled) { - audioManager.startBluetoothSco(); - audioManager.setBluetoothScoOn(true); - } else { - audioManager.setBluetoothScoOn(false); - audioManager.stopBluetoothSco(); - } - } - - @Override - public void start(AudioModeModule audioModeModule) { - JitsiMeetLogger.i("Using " + TAG + " as the audio device handler"); - - module = audioModeModule; - Context context = module.getContext(); - - // Setup runtime device change detection. - // - - // Detect changes in wired headset connections. - IntentFilter wiredHeadSetFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG); - BroadcastReceiver wiredHeadsetReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - JitsiMeetLogger.d(TAG + " Wired headset added / removed"); - onHeadsetDeviceChange(); - } - }; - context.registerReceiver(wiredHeadsetReceiver, wiredHeadSetFilter); - - // Detect Bluetooth device changes. - bluetoothHeadsetMonitor = new BluetoothHeadsetMonitor(context, this); - - // On Android < M, detect if we have an earpiece. - PackageManager pm = context.getPackageManager(); - if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { - module.addDevice(AudioModeModule.DEVICE_EARPIECE); - } - - // Always assume there is a speaker. - module.addDevice(AudioModeModule.DEVICE_SPEAKER); - } - - @Override - public void stop() { - bluetoothHeadsetMonitor.stop(); - bluetoothHeadsetMonitor = null; - } - - @Override - public void setAudioRoute(String device) { - // Turn speaker on / off - audioManager.setSpeakerphoneOn(device.equals(AudioModeModule.DEVICE_SPEAKER)); - - // Turn bluetooth on / off - setBluetoothAudioRoute(device.equals(AudioModeModule.DEVICE_BLUETOOTH)); - } - - @Override - public boolean setMode(int mode) { - if (mode == AudioModeModule.DEFAULT) { - audioFocusLost = false; - audioManager.setMode(AudioManager.MODE_NORMAL); - audioManager.abandonAudioFocus(this); - audioManager.setSpeakerphoneOn(false); - setBluetoothAudioRoute(false); - - return true; - } - - audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); - audioManager.setMicrophoneMute(false); - - if (audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN) - == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { - JitsiMeetLogger.w(TAG + " Audio focus request failed"); - return false; - } - - return true; - } -} diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java index 4be0f985c..8cbecd71d 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/AudioModeModule.java @@ -222,10 +222,8 @@ class AudioModeModule extends ReactContextBaseJavaModule { if (useConnectionService()) { audioDeviceHandler = new AudioDeviceHandlerConnectionService(audioManager); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - audioDeviceHandler = new AudioDeviceHandlerGeneric(audioManager); } else { - audioDeviceHandler = new AudioDeviceHandlerLegacy(audioManager); + audioDeviceHandler = new AudioDeviceHandlerGeneric(audioManager); } audioDeviceHandler.start(this); @@ -427,15 +425,6 @@ class AudioModeModule extends ReactContextBaseJavaModule { } } - /** - * Needed on the legacy handler... - * - * @return Context for the application. - */ - Context getContext() { - return getReactApplicationContext(); - } - /** * Interface for the modules implementing the actual audio device management. */ diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java deleted file mode 100644 index 7d713883d..000000000 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/BluetoothHeadsetMonitor.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright @ 2017-present 8x8, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jitsi.meet.sdk; - -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; - -import org.jitsi.meet.sdk.log.JitsiMeetLogger; - -/** - * Helper class to detect and handle Bluetooth device changes. It monitors - * Bluetooth headsets being connected / disconnected and notifies the module - * about device changes when this occurs. - */ -class BluetoothHeadsetMonitor { - private final static String TAG = BluetoothHeadsetMonitor.class.getSimpleName(); - - /** - * The {@link Context} in which this module executes. - */ - private final Context context; - - /** - * Reference to the {@link BluetoothAdapter} object, used to access Bluetooth functionality. - */ - private BluetoothAdapter adapter; - - /** - * Reference to a proxy object which allows us to query connected devices. - */ - private BluetoothHeadset headset; - - /** - * receiver registered for receiving Bluetooth connection state changes. - */ - private BroadcastReceiver receiver; - - /** - * Listener for receiving Bluetooth device change events. - */ - private Listener listener; - - public BluetoothHeadsetMonitor(Context context, Listener listener) { - this.context = context; - this.listener = listener; - } - - private boolean getBluetoothHeadsetProfileProxy() { - adapter = BluetoothAdapter.getDefaultAdapter(); - - if (adapter == null) { - JitsiMeetLogger.w(TAG + " Device doesn't support Bluetooth"); - return false; - } - - // XXX: The profile listener listens for system services of the given - // type being available to the application. That is, if our Bluetooth - // adapter has the "headset" profile. - BluetoothProfile.ServiceListener listener - = new BluetoothProfile.ServiceListener() { - @Override - public void onServiceConnected(int profile, BluetoothProfile proxy) { - if (profile == BluetoothProfile.HEADSET) { - headset = (BluetoothHeadset) proxy; - updateDevices(); - } - } - - @Override - public void onServiceDisconnected(int profile) { - // The logic is the same as the logic of onServiceConnected. - onServiceConnected(profile, /* proxy */ null); - } - }; - - return adapter.getProfileProxy(context, listener, BluetoothProfile.HEADSET); - } - - private void onBluetoothReceiverReceive(Context context, Intent intent) { - final String action = intent.getAction(); - - if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) { - // XXX: This action will be fired when a Bluetooth headset is - // connected or disconnected to the system. This is not related to - // audio routing. - int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -99); - - switch (state) { - case BluetoothHeadset.STATE_CONNECTED: - case BluetoothHeadset.STATE_DISCONNECTED: - JitsiMeetLogger.d(TAG + " BT headset connection state changed: " + state); - updateDevices(); - break; - } - } else if (action.equals(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)) { - // XXX: This action will be fired when the connection established - // with a Bluetooth headset (called a SCO connection) changes state. - // When the SCO connection is active we route audio to it. - int state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -99); - - switch (state) { - case AudioManager.SCO_AUDIO_STATE_CONNECTED: - case AudioManager.SCO_AUDIO_STATE_DISCONNECTED: - JitsiMeetLogger.d(TAG + " BT SCO connection state changed: " + state); - updateDevices(); - break; - } - } - } - - private void registerBluetoothReceiver() { - receiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - onBluetoothReceiverReceive(context, intent); - } - }; - - IntentFilter filter = new IntentFilter(); - filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - - context.registerReceiver(receiver, filter); - } - - /** - * Detects if there are new devices connected / disconnected and fires the - * {@link Listener} registered event. - */ - private void updateDevices() { - boolean headsetAvailable = (headset != null) && !headset.getConnectedDevices().isEmpty(); - listener.onBluetoothDeviceChange(headsetAvailable); - } - - public void start() { - AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - - if (!audioManager.isBluetoothScoAvailableOffCall()) { - JitsiMeetLogger.w(TAG + " Bluetooth SCO is not available"); - return; - } - - if (!getBluetoothHeadsetProfileProxy()) { - JitsiMeetLogger.w(TAG + " Couldn't get BT profile proxy"); - return; - } - - registerBluetoothReceiver(); - - // Initial detection. - updateDevices(); - } - - public void stop() { - if (receiver != null) { - context.unregisterReceiver(receiver); - } - - if (adapter != null && headset != null) { - adapter.closeProfileProxy(BluetoothProfile.HEADSET, headset); - } - - receiver = null; - headset = null; - adapter = null; - } - - interface Listener { - void onBluetoothDeviceChange(boolean deviceAvailable); - } -} diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivityDelegate.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivityDelegate.java index 7b214320a..1a1147f39 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivityDelegate.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetActivityDelegate.java @@ -1,6 +1,5 @@ /* - * Copyright @ 2019-present 8x8, Inc. - * Copyright @ 2018 Atlassian Pty Ltd + * Copyright @ 2018-present 8x8, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +16,8 @@ package org.jitsi.meet.sdk; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Intent; -import android.os.Build; import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.Callback; @@ -178,7 +175,6 @@ public class JitsiMeetActivityDelegate { }; } - @TargetApi(Build.VERSION_CODES.M) public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) { permissionListener = listener; activity.requestPermissions(permissions, requestCode); diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java index 9c1bb2bd0..e69004611 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java @@ -1,6 +1,5 @@ /* - * Copyright @ 2018-present 8x8, Inc. - * Copyright @ 2017-2018 Atlassian Pty Ltd + * Copyright @ 2017-present 8x8, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -126,7 +125,7 @@ public class JitsiMeetView extends BaseReactView = ReactInstanceManagerHolder.getNativeModule( PictureInPictureModule.class); if (pipModule != null - && PictureInPictureModule.isPictureInPictureSupported() + && pipModule.isPictureInPictureSupported() && !JitsiMeetActivityDelegate.arePermissionsBeingRequested() && this.url != null) { try { diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java index 2123334d5..a8edcd957 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/PictureInPictureModule.java @@ -1,5 +1,5 @@ /* - * Copyright @ 2017-present Atlassian Pty Ltd + * Copyright @ 2017-present 8x8, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.jitsi.meet.sdk; import android.annotation.TargetApi; import android.app.Activity; +import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.os.Build; import android.util.Rational; @@ -30,20 +31,41 @@ import com.facebook.react.module.annotations.ReactModule; import org.jitsi.meet.sdk.log.JitsiMeetLogger; +import java.util.HashMap; +import java.util.Map; + +import static android.content.Context.ACTIVITY_SERVICE; + @ReactModule(name = PictureInPictureModule.NAME) -class PictureInPictureModule - extends ReactContextBaseJavaModule { +class PictureInPictureModule extends ReactContextBaseJavaModule { public static final String NAME = "PictureInPicture"; - private static final String TAG = NAME; - static boolean isPictureInPictureSupported() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - } + private static boolean isSupported; public PictureInPictureModule(ReactApplicationContext reactContext) { super(reactContext); + + ActivityManager am = (ActivityManager) reactContext.getSystemService(ACTIVITY_SERVICE); + + // Android Go devices don't support PiP. There doesn't seem to be a better way to detect it than + // to use ActivityManager.isLowRamDevice(). + // https://stackoverflow.com/questions/58340558/how-to-detect-android-go + isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !am.isLowRamDevice(); + } + + /** + * Gets a {@code Map} of constants this module exports to JS. Supports JSON + * types. + * + * @return a {@link Map} of constants this module exports to JS + */ + @Override + public Map getConstants() { + Map constants = new HashMap<>(); + constants.put("SUPPORTED", isSupported); + return constants; } /** @@ -61,7 +83,7 @@ class PictureInPictureModule */ @TargetApi(Build.VERSION_CODES.O) public void enterPictureInPicture() { - if (!isPictureInPictureSupported()) { + if (!isSupported) { throw new IllegalStateException("Picture-in-Picture not supported"); } @@ -104,6 +126,10 @@ class PictureInPictureModule } } + public boolean isPictureInPictureSupported() { + return isSupported; + } + @Override public String getName() { return NAME; diff --git a/android/sdk/src/main/java/org/jitsi/meet/sdk/ProximityModule.java b/android/sdk/src/main/java/org/jitsi/meet/sdk/ProximityModule.java index 34669cf26..68e4354b0 100644 --- a/android/sdk/src/main/java/org/jitsi/meet/sdk/ProximityModule.java +++ b/android/sdk/src/main/java/org/jitsi/meet/sdk/ProximityModule.java @@ -1,5 +1,5 @@ /* - * Copyright @ 2017-present Atlassian Pty Ltd + * Copyright @ 2017-present 8x8, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,21 +33,10 @@ import com.facebook.react.module.annotations.ReactModule; * is used with the conference audio-only mode. */ @ReactModule(name = ProximityModule.NAME) -class ProximityModule - extends ReactContextBaseJavaModule { +class ProximityModule extends ReactContextBaseJavaModule { public static final String NAME = "Proximity"; - /** - * This type of wake lock (the one activated by the proximity sensor) has - * been available for a while, but the constant was only exported in API - * level 21 (Android Marshmallow) so make no assumptions and use its value - * directly. - * - * TODO: Remove when we bump the API level to 21. - */ - private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32; - /** * {@link WakeLock} instance. */ @@ -71,7 +60,7 @@ class ProximityModule try { wakeLock = powerManager.newWakeLock( - PROXIMITY_SCREEN_OFF_WAKE_LOCK, + PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "jitsi:"+NAME); } catch (Throwable ignored) { wakeLock = null; diff --git a/conference.js b/conference.js index 5fb3a93eb..d4a5c4790 100644 --- a/conference.js +++ b/conference.js @@ -411,6 +411,10 @@ function disconnect() { return Promise.resolve(); }; + if (!connection) { + return onDisconnected(); + } + return connection.disconnect().then(onDisconnected, onDisconnected); } @@ -755,7 +759,13 @@ export default { } if (isPrejoinPageEnabled(APP.store.getState())) { - _connectionPromise = connect(roomName); + _connectionPromise = connect(roomName).then(c => { + // we want to initialize it early, in case of errors to be able + // to gather logs + APP.connection = c; + + return c; + }); const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions); const tracks = await tryCreateLocalTracks; @@ -1202,10 +1212,6 @@ export default { // end used by torture - getLogs() { - return room.getLogs(); - }, - /** * Download logs, a function that can be called from console while * debugging. @@ -1214,7 +1220,7 @@ export default { saveLogs(filename = 'meetlog.json') { // this can be called from console and will not have reference to this // that's why we reference the global var - const logs = APP.conference.getLogs(); + const logs = APP.connection.getLogs(); const data = encodeURIComponent(JSON.stringify(logs, null, ' ')); const elem = document.createElement('a'); @@ -1584,10 +1590,6 @@ export default { if (didHaveVideo) { promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] })) .then(([ stream ]) => this.useVideoStream(stream)) - .then(() => { - sendAnalytics(createScreenSharingEvent('stopped')); - logger.log('Screen sharing stopped.'); - }) .catch(error => { logger.error('failed to switch back to local video', error); @@ -1604,6 +1606,8 @@ export default { return promise.then( () => { this.videoSwitchInProgress = false; + sendAnalytics(createScreenSharingEvent('stopped')); + logger.info('Screen sharing stopped.'); }, error => { this.videoSwitchInProgress = false; diff --git a/config.js b/config.js index 4cfbde296..eb9533751 100644 --- a/config.js +++ b/config.js @@ -37,6 +37,8 @@ var config = { clientNode: 'http://jitsi.org/jitsimeet', // The real JID of focus participant - can be overridden here + // Do not change username - FIXME: Make focus username configurable + // https://github.com/jitsi/jitsi-meet/issues/7376 // focusUserJid: 'focus@auth.jitsi-meet.example.com', @@ -44,6 +46,10 @@ var config = { // testing: { + // Disables the End to End Encryption feature. Useful for debugging + // issues related to insertable streams. + // disableE2EE: false, + // P2P test mode disables automatic switching to P2P when there are 2 // participants in the conference. p2pTestMode: false @@ -107,11 +113,20 @@ var config = { // participants and to enable it back a reload is needed. // startSilent: false + // Sets the preferred target bitrate for the Opus audio codec by setting its + // 'maxaveragebitrate' parameter. Currently not available in p2p mode. + // Valid values are in the range 6000 to 510000 + // opusMaxAverageBitrate: 20000, + // Video // Sets the preferred resolution (height) for local video. Defaults to 720. // resolution: 720, + // How many participants while in the tile view mode, before the receiving video quality is reduced from HD to SD. + // Use -1 to disable. + // maxFullResolutionParticipants: 2 + // w3c spec-compliant video constraints to use for video capture. Currently // used by browsers that return true from lib-jitsi-meet's // util#browser#usesNewGumFlow. The constraints are independent from @@ -201,6 +216,37 @@ var config = { // Default value for the channel "last N" attribute. -1 for unlimited. channelLastN: -1, + // Provides a way to use different "last N" values based on the number of participants in the conference. + // The keys in an Object represent number of participants and the values are "last N" to be used when number of + // participants gets to or above the number. + // + // For the given example mapping, "last N" will be set to 20 as long as there are at least 5, but less than + // 29 participants in the call and it will be lowered to 15 when the 30th participant joins. The 'channelLastN' + // will be used as default until the first threshold is reached. + // + // lastNLimits: { + // 5: 20, + // 30: 15, + // 50: 10, + // 70: 5, + // 90: 2 + // }, + + // Specify the settings for video quality optimizations on the client. + // videoQuality: { + // + // // Provides a way to configure the maximum bitrates that will be enforced on the simulcast streams for + // // video tracks. The keys in the object represent the type of the stream (LD, SD or HD) and the values + // // are the max.bitrates to be set on that particular type of stream. The actual send may vary based on + // // the available bandwidth calculated by the browser, but it will be capped by the values specified here. + // // This is currently not implemented on app based clients on mobile. + // maxBitratesVideo: { + // low: 200000, + // standard: 500000, + // high: 1500000 + // } + // }, + // // Options for the recording limit notification. // recordingLimit: { // @@ -303,10 +349,10 @@ var config = { // and microsoftApiApplicationClientID // enableCalendarIntegration: false, - // When 'true', it shows an intermediate page before joining, where the user can configure its devices. + // When 'true', it shows an intermediate page before joining, where the user can configure their devices. // prejoinPageEnabled: false, - // If true, shows the unsafe roon name warning label when a room name is + // If true, shows the unsafe room name warning label when a room name is // deemed unsafe (due to the simplicity in the name) and a password is not // set or the lobby is not enabled. // enableInsecureRoomNameWarning: false, @@ -328,10 +374,10 @@ var config = { // callStatsID: '', // callStatsSecret: '', - // enables sending participants display name to callstats + // Enables sending participants' display names to callstats // enableDisplayNameInStats: false, - // enables sending participants email if available to callstats and other analytics + // Enables sending participants' emails (if available) to callstats and other analytics // enableEmailInStats: false, // Privacy @@ -361,7 +407,7 @@ var config = { // The STUN servers that will be used in the peer to peer connections stunServers: [ - // { urls: 'stun:jitsi-meet.example.com:4446' }, + // { urls: 'stun:jitsi-meet.example.com:3478' }, { urls: 'stun:meet-jit-si-turnrelay.jitsi.net:443' } ] @@ -397,6 +443,15 @@ var config = { // The Amplitude APP Key: // amplitudeAPPKey: '' + // Configuration for the rtcstats server: + // In order to enable rtcstats one needs to provide a endpoint url. + // rtcstatsEndpoint: wss://rtcstats-server-pilot.jitsi.net/, + + // The interval at which rtcstats will poll getStats, defaults to 1000ms. + // If the value is set to 0 getStats won't be polled and the rtcstats client + // will only send data related to RTCPeerConnection events. + // rtcstatsPolIInterval: 1000 + // Array of script URLs to load as lib-jitsi-meet "analytics handlers". // scriptURLs: [ // "libs/analytics-ga.min.js", // google-analytics @@ -504,7 +559,7 @@ var config = { /** External API url used to receive branding specific information. If there is no url set or there are missing fields, the defaults are applied. - None of the fieds are mandatory and the response must have the shape: + None of the fields are mandatory and the response must have the shape: { // The hex value for the colour used as background backgroundColor: '#fff', @@ -518,6 +573,11 @@ var config = { */ // brandingDataUrl: '', + // The URL of the moderated rooms microservice, if available. If it + // is present, a link to the service will be rendered on the welcome page, + // otherwise the app doesn't render it. + // moderatedRoomServiceUrl: 'https://moderated.jitsi-meet.example.com', + // List of undocumented settings used in jitsi-meet /** _immediateReloadThreshold diff --git a/connection.js b/connection.js index 8c92bfb97..e51c1e67e 100644 --- a/connection.js +++ b/connection.js @@ -1,7 +1,7 @@ /* global APP, JitsiMeetJS, config */ +import { jitsiLocalStorage } from '@jitsi/js-utils'; import Logger from 'jitsi-meet-logger'; -import { jitsiLocalStorage } from 'js-utils'; import AuthHandler from './modules/UI/authentication/AuthHandler'; import { @@ -13,6 +13,7 @@ import { JitsiConnectionErrors, JitsiConnectionEvents } from './react/features/base/lib-jitsi-meet'; +import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions'; const logger = Logger.getLogger(__filename); @@ -81,7 +82,7 @@ function checkForAttachParametersAndConnect(id, password, connection) { */ function connect(id, password, roomName) { const connectionConfig = Object.assign({}, config); - const { issuer, jwt } = APP.store.getState()['features/base/jwt']; + const { jwt } = APP.store.getState()['features/base/jwt']; // Use Websocket URL for the web app if configured. Note that there is no 'isWeb' check, because there's assumption // that this code executes only on web browsers/electron. This needs to be changed when mobile and web are unified. @@ -93,11 +94,7 @@ function connect(id, password, roomName) { // in future). It's included for the time being for Jitsi Meet and lib-jitsi-meet versions interoperability. connectionConfig.serviceUrl = connectionConfig.bosh = serviceUrl; - const connection - = new JitsiMeetJS.JitsiConnection( - null, - jwt && issuer && issuer !== 'anonymous' ? jwt : undefined, - connectionConfig); + const connection = new JitsiMeetJS.JitsiConnection(null, jwt, connectionConfig); if (config.iAmRecorder) { connection.addFeature(DISCO_JIBRI_FEATURE); @@ -113,6 +110,10 @@ function connect(id, password, roomName) { connection.addEventListener( JitsiConnectionEvents.CONNECTION_FAILED, connectionFailedHandler); + connection.addEventListener( + JitsiConnectionEvents.DISPLAY_NAME_REQUIRED, + displayNameRequiredHandler + ); /* eslint-disable max-params */ /** @@ -166,6 +167,14 @@ function connect(id, password, roomName) { reject(err); } + /** + * Marks the display name for the prejoin screen as required. + * This can happen if a user tries to join a room with lobby enabled. + */ + function displayNameRequiredHandler() { + APP.store.dispatch(setPrejoinDisplayNameRequired()); + } + checkForAttachParametersAndConnect(id, password, connection); }); } @@ -198,10 +207,9 @@ export function openConnection({ id, password, retry, roomName }) { return connect(id, password, roomName).catch(err => { if (retry) { - const { issuer, jwt } = APP.store.getState()['features/base/jwt']; + const { jwt } = APP.store.getState()['features/base/jwt']; - if (err === JitsiConnectionErrors.PASSWORD_REQUIRED - && (!jwt || issuer === 'anonymous')) { + if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) { return AuthHandler.requestAuth(roomName, connect); } } diff --git a/css/_atlaskit_overrides.scss b/css/_atlaskit_overrides.scss index 0b110c0a3..b06b8586e 100644 --- a/css/_atlaskit_overrides.scss +++ b/css/_atlaskit_overrides.scss @@ -2,7 +2,7 @@ * Move the @atlaskit/flag container up a little bit so it does not cover the * toolbar with the first notification. */ -.cjMOOK{ +.jIMojv{ bottom: calc(#{$newToolbarSizeWithPadding}) !important; } diff --git a/css/_base.scss b/css/_base.scss index a717cca1f..50d9169f2 100644 --- a/css/_base.scss +++ b/css/_base.scss @@ -33,6 +33,26 @@ body { } } +/** + * AtlasKit sets a default margin on the rendered modals, so + * when the shift-right class is set when the chat opens, we + * pad the modal container in order for the modals to be centered + * while also taking the chat size into consideration. +*/ +@media (min-width: 480px + $sidebarWidth) { + .shift-right [class^="Modal__FillScreen"] { + padding-left: $sidebarWidth; + } +} + +/** + * Similarly, we offset the notifications when the chat is open by + * padding the container. +*/ +.shift-right [class^="styledFlagGroup-"] { + padding-left: $sidebarWidth; +} + .jitsi-icon svg { fill: white; } diff --git a/css/_chat.scss b/css/_chat.scss index 9b9444843..3c28d1758 100644 --- a/css/_chat.scss +++ b/css/_chat.scss @@ -4,16 +4,11 @@ color: #FFF; display: flex; flex-direction: column; - /** - * Make the sidebar flush with the top of the toolbar. Take the size of - * the toolbar and subtract from 100%. - */ - height: calc(100% - #{$newToolbarSizeWithPadding}); + height: 100%; left: -$sidebarWidth; overflow: hidden; position: absolute; top: 0; - transition: left 0.5s; width: $sidebarWidth; z-index: $sideToolbarContainerZ; diff --git a/css/_prejoin-dialog.scss b/css/_prejoin-dialog.scss index 5c084bcfc..3e77ca271 100644 --- a/css/_prejoin-dialog.scss +++ b/css/_prejoin-dialog.scss @@ -96,6 +96,11 @@ padding: 0 8px; } } + + .prejoin-dialog-btn.primary, + .action-btn.prejoin-dialog-btn.text { + width: 310px; + } } .prejoin-dialog-callout { diff --git a/css/_prejoin.scss b/css/_prejoin.scss index 52a34be64..9317c3f89 100644 --- a/css/_prejoin.scss +++ b/css/_prejoin.scss @@ -36,13 +36,7 @@ } &-checkbox-container { - align-items: center; - color: #fff; - display: none; - font-size: 13px; - justify-content: center; - line-height: 20px; - margin-top: 16px; + margin-bottom: 14px; width: 100%; } } diff --git a/css/_premeeting-screens.scss b/css/_premeeting-screens.scss index a93691854..c892100d6 100644 --- a/css/_premeeting-screens.scss +++ b/css/_premeeting-screens.scss @@ -1,22 +1,90 @@ /** * Shared style for full screen local track based dialogs/modals. */ + .premeeting-screen, + .preview-overlay { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + } + .premeeting-screen { align-items: stretch; - background: #1C2025; - bottom: 0; + background: radial-gradient(50% 50% at 50% 50%, #5D95C7 0%, #376288 100%), #FFFFFF; display: flex; flex-direction: column; font-size: 1.3em; - left: 0; - position: absolute; - right: 0; - top: 0; z-index: $toolbarZ + 1; + .action-btn { + border-radius: 3px; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 15px; + line-height: 24px; + margin-top: 16px; + padding: 7px 16px; + position: relative; + text-align: center; + width: 286px; + + &.primary { + background: #0376DA; + border: 1px solid #0376DA; + } + + &.secondary { + background: transparent; + border: 1px solid #5E6D7A; + } + + &.text { + width: auto; + font-size: 13px; + margin: 0; + padding: 0; + } + + &.disabled { + background: #5E6D7A; + border: 1px solid #5E6D7A; + color: #AFB6BC; + cursor: initial; + + .icon { + & > svg { + fill: #AFB6BC; + } + } + + .options { + border-left: 1px solid #AFB6BC; + } + } + + .options { + align-items: center; + border-left: 1px solid #fff; + display: flex; + height: 100%; + justify-content: center; + position: absolute; + right: 0; + top: 0; + width: 40px; + } + } + + .preview-overlay { + background-image: linear-gradient(transparent, black); + z-index: $toolbarZ + 1; + } + .content { align-items: center; - background-image: linear-gradient(transparent, black); display: flex; flex: 1; flex-direction: column; @@ -97,66 +165,6 @@ color: $defaultWarningColor; } } - - .action-btn { - border-radius: 3px; - color: #fff; - cursor: pointer; - display: inline-block; - font-size: 15px; - line-height: 24px; - margin-top: 16px; - padding: 7px 16px; - position: relative; - text-align: center; - width: 286px; - - &.primary { - background: #0376DA; - border: 1px solid #0376DA; - } - - &.secondary { - background: transparent; - border: 1px solid #5E6D7A; - } - - &.text { - width: auto; - font-size: 13px; - margin: 0; - padding: 0; - } - - &.disabled { - background: #5E6D7A; - border: 1px solid #5E6D7A; - color: #AFB6BC; - cursor: initial; - - .icon { - & > svg { - fill: #AFB6BC; - } - } - - .options { - border-left: 1px solid #AFB6BC; - } - } - - .options { - align-items: center; - border-left: 1px solid #fff; - display: flex; - height: 100%; - justify-content: center; - position: absolute; - right: 0; - top: 0; - width: 40px; - } - } } .media-btn-container { @@ -189,9 +197,16 @@ text-align: center; } + .preview-avatar-container { + width: 100%; + height: 80%; + display: flex; + align-items: center; + justify-content: center; + } + .avatar { background: #A4B8D1; - margin: 200px auto 0 auto; } video { @@ -201,3 +216,66 @@ width: 100%; } } + +@mixin flex-centered() { + align-items: center; + display: flex; + justify-content: center; +} + +@mixin icon-container($bg, $fill) { + .toggle-button-icon-container { + background: $bg; + + svg { + fill: $fill + } + } +} + +.toggle-button { + border-radius: 3px; + cursor: pointer; + color: #fff; + font-size: 13px; + height: 40px; + margin: 0 auto; + width: 320px; + + @include flex-centered(); + + svg { + fill: transparent; + } + + &:hover { + background: #1C2025; + + @include icon-container(#A4B8D1, #1C2025); + } + + &-container { + position: relative; + + @include flex-centered(); + } + + &-icon-container { + border-radius: 50%; + left: -22px; + padding: 2px; + position: absolute; + } + + &--toggled { + background: #75757A; + + &:hover { + background: #75757A; + + @include icon-container(#A4B8D1, #75757A); + } + + @include icon-container(#A4B8D1, #75757A); + } +} diff --git a/css/_responsive.scss b/css/_responsive.scss new file mode 100644 index 000000000..01fd767b0 --- /dev/null +++ b/css/_responsive.scss @@ -0,0 +1,70 @@ +@media only screen and (max-width: $smallScreen) { + .watermark { + width: 20%; + height: 20%; + } + + .new-toolbox { + .toolbox-content { + .button-group-center, .button-group-left, .button-group-right { + .toolbox-button { + .toolbox-icon { + width: 28px; + height: 28px; + svg { + width: 18px; + height: 18px; + } + } + + &:nth-child(2) { + .toolbox-icon { + width: 30px; + height: 30px; + } + } + } + } + } + } +} + +@media only screen and (max-width: $verySmallScreen) { + #videoResolutionLabel { + display: none; + } + .desktop-browser { + .vertical-filmstrip .filmstrip { + display: none; + } + } + .new-toolbox { + .toolbox-content { + .button-group-center, .button-group-left, .button-group-right { + .settings-button-small-icon { + display: none; + } + .toolbox-button { + .toolbox-icon { + width: 18px; + height: 18px; + svg { + width: 12px; + height: 12px; + } + } + + &:nth-child(2) { + .toolbox-icon { + width: 20px; + height: 20px; + } + } + } + } + } + } + .chrome-extension-banner { + display: none; + } +} diff --git a/css/_toolbars.scss b/css/_toolbars.scss index 9ec65f25b..928a68d28 100644 --- a/css/_toolbars.scss +++ b/css/_toolbars.scss @@ -42,6 +42,11 @@ display: none; } + &.shift-right { + margin-left: $sidebarWidth; + width: calc(100% - #{$sidebarWidth}); + } + .toolbox-background { background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0)); transition: bottom .3s ease-in; diff --git a/css/_variables.scss b/css/_variables.scss index b5cc22c36..e20787aba 100644 --- a/css/_variables.scss +++ b/css/_variables.scss @@ -164,6 +164,9 @@ $unsupportedDesktopBrowserTextFontSize: 21px; $watermarkWidth: 186px; $watermarkHeight: 74px; +$welcomePageWatermarkWidth: 186px; +$welcomePageWatermarkHeight: 74px; + /** * Welcome page variables. */ @@ -178,9 +181,12 @@ $welcomePageHeaderBackgroundPosition: none; $welcomePageHeaderBackgroundRepeat: none; $welcomePageHeaderBackgroundSize: none; $welcomePageHeaderPaddingBottom: 0px; +$welcomePageHeaderMinHeight: fit-content; $welcomePageHeaderTextMarginTop: 35px; $welcomePageHeaderTextMarginBottom: 35px; +$welcomePageHeaderTextDisplay: flex; +$welcomePageHeaderTextWidth: 650px; $welcomePageHeaderTextTitleMarginBottom: 16px; $welcomePageHeaderTextTitleFontSize: 2.5rem; @@ -195,6 +201,7 @@ $welcomePageHeaderTextDescriptionLineHeight: 24px; $welcomePageHeaderTextDescriptionMarginBottom: 20px; $welcomePageHeaderTextDescriptionAlignSelf: inherit; +$welcomePageEnterRoomDisplay: flex; $welcomePageEnterRoomWidth: 680px; $welcomePageEnterRoomPadding: 25px 30px; $welcomePageEnterRoomBorderRadius: 0px; @@ -269,3 +276,9 @@ $chromeExtensionBannerTop: 80px; $chromeExtensionBannerRight: 16px; $chromeExtensionBannerTopInMeeting: 10px; $chromeExtensionBannerRightInMeeeting: 10px; + +/** +* media type thresholds +*/ +$smallScreen: 700px; +$verySmallScreen: 500px; diff --git a/css/_videolayout_default.scss b/css/_videolayout_default.scss index 67078ea20..c9d538fcb 100644 --- a/css/_videolayout_default.scss +++ b/css/_videolayout_default.scss @@ -181,6 +181,13 @@ visibility: hidden; z-index: $zindex2; } + + &.shift-right { + &#largeVideoContainer { + margin-left: $sidebarWidth; + width: calc(100% - #{$sidebarWidth}); + } + } } #localVideoWrapper { diff --git a/css/_welcome_page.scss b/css/_welcome_page.scss index 2a5145719..a1171aa1e 100644 --- a/css/_welcome_page.scss +++ b/css/_welcome_page.scss @@ -21,18 +21,18 @@ body.welcome-page { align-items: center; display: flex; flex-direction: column; - min-height: fit-content; + min-height: $welcomePageHeaderMinHeight; overflow: hidden; position: relative; text-align: center; .header-text { - display: flex; + display: $welcomePageHeaderTextDisplay; flex-direction: column; margin-top: $watermarkHeight + $welcomePageHeaderTextMarginTop; margin-bottom: $welcomePageHeaderTextMarginBottom; max-width: calc(100% - 40px); - width: 650px; + width: $welcomePageHeaderTextWidth; z-index: $zindex2; } @@ -56,7 +56,7 @@ body.welcome-page { } #enter_room { - display: flex; + display: $welcomePageEnterRoomDisplay; align-items: center; max-width: calc(100% - 40px); width: $welcomePageEnterRoomWidth; @@ -111,6 +111,22 @@ body.welcome-page { } + #moderated-meetings { + max-width: calc(100% - 40px); + padding: 16px 0 39px 0; + width: $welcomePageEnterRoomWidth; + + p { + color: $welcomePageDescriptionColor; + text-align: left; + + a { + color: inherit; + font-weight: 600; + } + } + } + .tab-container { font-size: 16px; position: relative; @@ -195,5 +211,10 @@ body.welcome-page { position: absolute; width: 100%; height: 100%; + + .watermark.leftwatermark { + width: $welcomePageWatermarkWidth; + height: $welcomePageWatermarkHeight; + } } } diff --git a/css/buttons/copy.scss b/css/buttons/copy.scss new file mode 100644 index 000000000..20867ce51 --- /dev/null +++ b/css/buttons/copy.scss @@ -0,0 +1,38 @@ +.copy-button { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 8px 8px 16px; + margin-top: 8px; + width: calc(100% - 24px); + height: 24px; + + background: #0376DA; + border-radius: 4px; + cursor: pointer; + + &:hover { + background: #278ADF; + font-weight: 600; + } + + &-content { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 292px; + margin-right: 16px; + + &.selected { + font-weight: 600; + } + } + + &.clicked { + background: #31B76A; + } + + & > div > svg > path { + fill: #fff; + } +} \ No newline at end of file diff --git a/css/filmstrip/_tile_view.scss b/css/filmstrip/_tile_view.scss index 0bdf505ee..619b3e232 100644 --- a/css/filmstrip/_tile_view.scss +++ b/css/filmstrip/_tile_view.scss @@ -46,7 +46,16 @@ position: fixed; top: 0; width: 100%; - z-index: $filmstripVideosZ + z-index: $filmstripVideosZ; + + &.shift-right { + margin-left: $sidebarWidth; + width: calc(100% - #{$sidebarWidth}); + + #filmstripRemoteVideos { + width: calc(100vw - #{$sidebarWidth}); + } + } } /** diff --git a/css/main.scss b/css/main.scss index 826fb9932..160342b7b 100644 --- a/css/main.scss +++ b/css/main.scss @@ -33,9 +33,11 @@ $flagsImagePath: "../images/"; @import 'inlay'; @import 'reload_overlay/reload_overlay'; @import 'mini_toolbox'; +@import 'buttons/copy.scss'; @import 'modals/desktop-picker/desktop-picker'; @import 'modals/device-selection/device-selection'; @import 'modals/dialog'; +@import 'modals/embed-meeting/embed-meeting'; @import 'modals/feedback/feedback'; @import 'modals/invite/info'; @import 'modals/settings/settings'; @@ -99,5 +101,6 @@ $flagsImagePath: "../images/"; @import 'modals/security/security'; @import 'premeeting-screens'; @import 'e2ee'; +@import 'responsive'; /* Modules END */ diff --git a/css/modals/embed-meeting/_embed-meeting.scss b/css/modals/embed-meeting/_embed-meeting.scss new file mode 100644 index 000000000..3d3a7f21a --- /dev/null +++ b/css/modals/embed-meeting/_embed-meeting.scss @@ -0,0 +1,59 @@ +.embed-meeting { + &-dialog { + display: flex; + flex-direction: column; + + &-header { + display: flex; + justify-content: space-between; + margin: 16px 16px 24px; + width: calc(100% - 32px); + color: #fff; + font-weight: 600; + font-size: 24px; + line-height: 32px; + + & > div > svg { + cursor: pointer; + fill: #A4B8D1; + } + } + } + + &-copy { + color: white; + font-size: 15px; + margin-left: auto; + margin-top: 16px; + width: auto; + } + + &-code { + background: transparent; + border: 1px solid #A4B8D1; + color: white; + font-size: 15px; + height: 165px; + line-height: 24px; + padding: 8px; + width: 100%; + resize: vertical; + } + + &-trigger { + display: flex; + align-items: center; + padding: 8px 8px 8px 16px; + margin-top: 24px; + width: calc(100% - 24px); + height: 24px; + background: #2A3A4B; + border: 1px solid #5E6D7A; + border-radius: 4px; + cursor: pointer; + + .jitsi-icon { + margin-right: 20px; + } + } +} diff --git a/css/modals/invite/_invite_more.scss b/css/modals/invite/_invite_more.scss index 824ed7f6b..742f4e898 100644 --- a/css/modals/invite/_invite_more.scss +++ b/css/modals/invite/_invite_more.scss @@ -47,10 +47,6 @@ font-size: 15px; line-height: 24px; - & > span { - font-weight: 600; - } - &.header { display: flex; justify-content: space-between; @@ -67,44 +63,6 @@ } } - &.copy-link { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 8px 8px 16px; - margin-top: 8px; - width: calc(100% - 24px); - height: 24px; - - background: #0376DA; - border-radius: 4px; - cursor: pointer; - - &:hover { - background: #278ADF; - font-weight: 600; - } - - &-text { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 292px; - - &.selected { - font-weight: 600; - } - } - - &.clicked { - background: #31B76A; - } - - & > div > svg > path { - fill: #fff; - } - } - &.separator { margin: 24px 0 24px -20px; padding: 0 20px; diff --git a/css/modals/security/_security.scss b/css/modals/security/_security.scss index 50d3eb321..cc8ea66c0 100644 --- a/css/modals/security/_security.scss +++ b/css/modals/security/_security.scss @@ -25,6 +25,10 @@ font-size: 14px; color: #6FB1EA; } + + & > :first-child:not(:last-child) { + margin-right: 24px; + } } } } diff --git a/css/modals/settings/_settings.scss b/css/modals/settings/_settings.scss index 9a81dffa5..d4545ea6f 100644 --- a/css/modals/settings/_settings.scss +++ b/css/modals/settings/_settings.scss @@ -30,10 +30,12 @@ width: 100%; } - .profile-edit-field, - .settings-sub-pane { + .profile-edit-field { flex: 1; } + .settings-sub-pane { + flex-grow: 1; + } .profile-edit-field { margin-right: 20px; diff --git a/debian/jitsi-meet-turnserver.postinst b/debian/jitsi-meet-turnserver.postinst index 4272647c5..ecb5bc03a 100644 --- a/debian/jitsi-meet-turnserver.postinst +++ b/debian/jitsi-meet-turnserver.postinst @@ -49,7 +49,7 @@ case "$1" in # nothing to do echo "------------------------------------------------" echo "" - echo "turnserver is listening on tcp 4445 as other nginx sites use port 443" + echo "turnserver is listening on tcp 5349 as other nginx sites use port 443" echo "" echo "------------------------------------------------" NGINX_MULTIPLEXING="false" @@ -87,9 +87,36 @@ case "$1" in if [[ -f $TURN_CONFIG ]] ; then echo "------------------------------------------------" echo "" - echo "turnserver is already configured on this machine, skipping." + echo "turnserver is already configured on this machine." echo "" echo "------------------------------------------------" + + if grep -q "jitsi-meet coturn config" "$TURN_CONFIG" && ! grep -q "jitsi-meet coturn relay disable config" "$TURN_CONFIG" ; then + echo "Updating coturn config" + echo "# jitsi-meet coturn relay disable config. Do not modify this line +no-multicast-peers +no-cli +no-loopback-peers +no-tcp-relay +denied-peer-ip=0.0.0.0-0.255.255.255 +denied-peer-ip=10.0.0.0-10.255.255.255 +denied-peer-ip=100.64.0.0-100.127.255.255 +denied-peer-ip=127.0.0.0-127.255.255.255 +denied-peer-ip=169.254.0.0-169.254.255.255 +denied-peer-ip=127.0.0.0-127.255.255.255 +denied-peer-ip=172.16.0.0-172.31.255.255 +denied-peer-ip=192.0.0.0-192.0.0.255 +denied-peer-ip=192.0.2.0-192.0.2.255 +denied-peer-ip=192.88.99.0-192.88.99.255 +denied-peer-ip=192.168.0.0-192.168.255.255 +denied-peer-ip=198.18.0.0-198.19.255.255 +denied-peer-ip=198.51.100.0-198.51.100.255 +denied-peer-ip=203.0.113.0-203.0.113.255 +denied-peer-ip=240.0.0.0-255.255.255.255" >> $TURN_CONFIG + + invoke-rc.d coturn restart || true + fi + db_stop exit 0 fi @@ -152,7 +179,7 @@ case "$1" in PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua" if [ -f $PROSODY_HOST_CONFIG ] ; then # If we are not multiplexing we need to change the port in prosody config - sed -i 's/"443"/"4445"/g' $PROSODY_HOST_CONFIG + sed -i 's/"443"/"5349"/g' $PROSODY_HOST_CONFIG invoke-rc.d prosody restart || true fi fi diff --git a/debian/jitsi-meet-web-config.postinst b/debian/jitsi-meet-web-config.postinst index 8d54c9145..c5199ae59 100644 --- a/debian/jitsi-meet-web-config.postinst +++ b/debian/jitsi-meet-web-config.postinst @@ -98,7 +98,7 @@ case "$1" in -reqexts SAN \ -extensions SAN \ -config <(cat /etc/ssl/openssl.cnf \ - <(printf '[SAN]\nsubjectAltName=DNS:localhost,DNS:$JVB_HOSTNAME,IP:$JVB_HOSTNAME')) + <(printf "[SAN]\nsubjectAltName=DNS:localhost,DNS:$JVB_HOSTNAME")) fi fi diff --git a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example index 3e8669f89..1e6163d88 100644 --- a/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example +++ b/doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example @@ -6,8 +6,8 @@ muc_mapper_domain_base = "jitmeet.example.com"; turncredentials_secret = "__turnSecret__"; turncredentials = { - { type = "stun", host = "jitmeet.example.com", port = "4446" }, - { type = "turn", host = "jitmeet.example.com", port = "4446", transport = "udp" }, + { type = "stun", host = "jitmeet.example.com", port = "3478" }, + { type = "turn", host = "jitmeet.example.com", port = "3478", transport = "udp" }, { type = "turns", host = "jitmeet.example.com", port = "443", transport = "tcp" } }; diff --git a/doc/debian/jitsi-meet-turn/turnserver.conf b/doc/debian/jitsi-meet-turn/turnserver.conf index 7b6590079..02dff699b 100644 --- a/doc/debian/jitsi-meet-turn/turnserver.conf +++ b/doc/debian/jitsi-meet-turn/turnserver.conf @@ -5,14 +5,32 @@ static-auth-secret=__turnSecret__ realm=jitsi-meet.example.com cert=/etc/jitsi/meet/jitsi-meet.example.com.crt pkey=/etc/jitsi/meet/jitsi-meet.example.com.key - +no-multicast-peers +no-cli +no-loopback-peers +no-tcp-relay no-tcp -listening-port=4446 -tls-listening-port=4445 +listening-port=3478 +tls-listening-port=5349 external-ip=__external_ip_address__ no-tlsv1 no-tlsv1_1 # https://ssl-config.mozilla.org/#server=haproxy&version=2.1&config=intermediate&openssl=1.1.0g&guideline=5.4 cipher-list=ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 - +# jitsi-meet coturn relay disable config. Do not modify this line +denied-peer-ip=0.0.0.0-0.255.255.255 +denied-peer-ip=10.0.0.0-10.255.255.255 +denied-peer-ip=100.64.0.0-100.127.255.255 +denied-peer-ip=127.0.0.0-127.255.255.255 +denied-peer-ip=169.254.0.0-169.254.255.255 +denied-peer-ip=127.0.0.0-127.255.255.255 +denied-peer-ip=172.16.0.0-172.31.255.255 +denied-peer-ip=192.0.0.0-192.0.0.255 +denied-peer-ip=192.0.2.0-192.0.2.255 +denied-peer-ip=192.88.99.0-192.88.99.255 +denied-peer-ip=192.168.0.0-192.168.255.255 +denied-peer-ip=198.18.0.0-198.19.255.255 +denied-peer-ip=198.51.100.0-198.51.100.255 +denied-peer-ip=203.0.113.0-203.0.113.255 +denied-peer-ip=240.0.0.0-255.255.255.255 syslog diff --git a/doc/debian/jitsi-meet/jitsi-meet.conf b/doc/debian/jitsi-meet/jitsi-meet.conf index 989d6a154..879fcf29e 100644 --- a/doc/debian/jitsi-meet/jitsi-meet.conf +++ b/doc/debian/jitsi-meet/jitsi-meet.conf @@ -7,7 +7,7 @@ stream { server 127.0.0.1:4444; } upstream turn { - server 127.0.0.1:4445; + server 127.0.0.1:5349; } # since 1.13.10 map $ssl_preread_alpn_protocols $upstream { diff --git a/doc/debian/jitsi-meet/jitsi-meet.example b/doc/debian/jitsi-meet/jitsi-meet.example index f8b9a56ae..9362b6b53 100644 --- a/doc/debian/jitsi-meet/jitsi-meet.example +++ b/doc/debian/jitsi-meet/jitsi-meet.example @@ -45,8 +45,10 @@ server { error_page 404 /static/404.html; gzip on; - gzip_types text/plain text/css application/javascript application/json; + gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm; gzip_vary on; + gzip_proxied no-cache no-store private expired auth; + gzip_min_length 512; location = /config.js { alias /etc/jitsi/meet/jitsi-meet.example.com-config.js; @@ -61,6 +63,11 @@ server { { add_header 'Access-Control-Allow-Origin' '*'; alias /usr/share/jitsi-meet/$1/$2; + + # cache all versioned files + if ($arg_v) { + expires 1y; + } } # BOSH diff --git a/doc/example-config-files/jitsi.example.com.example b/doc/example-config-files/jitsi.example.com.example index eddd796ce..2a197a0a2 100755 --- a/doc/example-config-files/jitsi.example.com.example +++ b/doc/example-config-files/jitsi.example.com.example @@ -14,6 +14,12 @@ server { ssi on; } + gzip on; + gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm; + gzip_vary on; + gzip_proxied no-cache no-store private expired auth; + gzip_min_length 512; + # BOSH location /http-bind { proxy_pass http://localhost:5280/http-bind; diff --git a/doc/example-config-files/multidomain/jitsi.example.com.multidomain.example b/doc/example-config-files/multidomain/jitsi.example.com.multidomain.example index c003f16fa..2e458501c 100644 --- a/doc/example-config-files/multidomain/jitsi.example.com.multidomain.example +++ b/doc/example-config-files/multidomain/jitsi.example.com.multidomain.example @@ -28,6 +28,12 @@ server { tcp_nodelay on; } + gzip on; + gzip_types text/plain text/css application/javascript application/json image/x-icon application/octet-stream application/wasm; + gzip_vary on; + gzip_proxied no-cache no-store private expired auth; + gzip_min_length 512; + location ~ ^/([^/?&:'"]+)$ { try_files $uri @root_path; } diff --git a/index.html b/index.html index 30cea926e..2a222ae99 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,17 @@ + + + + + + + + +
+ + diff --git a/webpack.config.js b/webpack.config.js index 49719fc97..7674fdf53 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,7 +55,7 @@ const config = { // as well. exclude: [ - new RegExp(`${__dirname}/node_modules/(?!js-utils)`) + new RegExp(`${__dirname}/node_modules/(?!@jitsi/js-utils)`) ], loader: 'babel-loader', options: {