Compare commits

..

1 Commits

Author SHA1 Message Date
Calin-Teodor
d0259c5333 chore(rn, versions): bump app and sdk versions 2024-02-29 16:37:20 +02:00
74 changed files with 1632 additions and 2423 deletions

3
.gitignore vendored
View File

@@ -99,7 +99,10 @@ tsconfig.json
#
react-native-sdk/*.tgz
react-native-sdk/android/src
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetOngoingConferenceService.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JitsiMeetReactNativePackage.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/JMOngoingConferenceModule.java
!react-native-sdk/android/src/main/java/org/jitsi/meet/sdk/RNOngoingNotification.java
react-native-sdk/images
react-native-sdk/ios
react-native-sdk/lang

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=24.0.1
sdkVersion=9.0.2
appVersion=24.0.0
sdkVersion=9.0.0

View File

@@ -242,7 +242,7 @@ class ReactInstanceManagerHolder {
options.videoDecoderFactory = new H264AndSoftwareVideoDecoderFactory(eglContext);
options.videoEncoderFactory = new H264AndSoftwareVideoEncoderFactory(eglContext);
options.enableMediaProjectionService = true;
// options.loggingSeverity = Logging.Severity.LS_INFO;
// options.loggingSeverity = Logging.Severity.LS_INFO;
Log.d(TAG, "initializing RN with Activity");

View File

@@ -329,8 +329,6 @@ var config = {
// configuration for all things recording related. Existing settings will be migrated here in the future.
// recordings: {
// // IF true (default) recording audio and video is selected by default in the recording dialog.
// // recordAudioAndVideo: true,
// // If true, shows a notification at the start of the meeting with a call to action button
// // to start recording (for users who can do so).
// // suggestRecording: true,
@@ -1302,8 +1300,6 @@ var config = {
// remoteVideoMenu: {
// // Whether the remote video context menu to be rendered or not.
// disabled: true,
// // If set to true the 'Switch to visitor' button will be disabled.
// disableDemote: true,
// // If set to true the 'Kick out' button will be disabled.
// disableKick: true,
// // If set to true the 'Grant moderator' button will be disabled.

View File

@@ -453,7 +453,7 @@ PODS:
- react-native-video/Video (6.0.0-alpha.11):
- PromisesSwift
- React-Core
- react-native-webrtc (118.0.3):
- react-native-webrtc (118.0.2):
- JitsiWebRTC (~> 118.0.0)
- React-Core
- react-native-webview (13.5.1):
@@ -881,7 +881,7 @@ SPEC CHECKSUMS:
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
react-native-webrtc: 6fc32f3d556aa60aa2334eeaf6cadcdab2432809
react-native-webrtc: a0a8a1730b6cc5a5bda8a6e2166a74c9b78029e2
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
React-NativeModulesApple: 4225ac31a26696c02c54b471052b3e85e74a9a0c
React-perflogger: cb433f318c6667060fc1f62e26eb58d6eb30a627

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>24.0.1</string>
<string>24.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>24.0.1</string>
<string>24.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>24.0.1</string>
<string>24.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>UISupportedInterfaceOrientations</key>

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>24.0.1</string>
<string>24.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CLKComplicationPrincipalClass</key>

View File

@@ -629,7 +629,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "WITH_ENVIRONMENT=\"../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
shellScript = "export NODE_BINARY=node\nexport NODE_ARGS=\"--max_old_space_size=4096\"\n../../node_modules/react-native/scripts/react-native-xcode.sh\n";
};
DE9A016B289A9A9A00E41CBB /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>9.0.2</string>
<string>9.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>9.0.2</string>
<string>9.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPrincipalClass</key>

View File

@@ -305,8 +305,6 @@
"contactSupport": "Contact support",
"copied": "Copied",
"copy": "Copy",
"demoteParticipantDialog": "Are you sure you want to move this participant to visitor?",
"demoteParticipantTitle": "Move to visitor",
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats your name?",
"done": "Done",
@@ -815,7 +813,6 @@
"videoUnmuteBlockedDescription": "Camera unmute and desktop sharing operation have been temporarily blocked because of system limits.",
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
"viewLobby": "View lobby",
"viewVisitors": "View visitors",
"waitingParticipants": "{{waitingParticipants}} people",
"whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.",
"whiteboardLimitTitle": "Whiteboard usage"
@@ -1422,7 +1419,6 @@
},
"videothumbnail": {
"connectionInfo": "Connection Info",
"demote": "Move to visitor",
"domute": "Mute",
"domuteOthers": "Mute everyone else",
"domuteVideo": "Disable camera",
@@ -1477,7 +1473,6 @@
"chatIndicator": "(visitor)",
"labelTooltip": "Number of visitors: {{count}}",
"notification": {
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
"description": "To participate raise your hand",
"title": "You are a visitor in the meeting"
}

26
package-lock.json generated
View File

@@ -54,14 +54,14 @@
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"i18next-browser-languagedetector": "3.0.1",
"i18next-http-backend": "2.2.1",
"i18next-http-backend": "^2.2.1",
"image-capture": "0.4.0",
"jquery": "3.6.1",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1784.0.0+639ad566/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -100,7 +100,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.3",
"react-native-webrtc": "118.0.2",
"react-native-webview": "13.5.1",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
@@ -12772,8 +12772,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"integrity": "sha512-rtXPegsdEOx7rxQnyxoony7BXD88ssM5prGPU2Ax6AChmzW933CZu/aW7m9bP4WSFHnVvABS3M6NEF76h282Nw==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1784.0.0+639ad566/lib-jitsi-meet.tgz",
"integrity": "sha512-1K0PIItt5u88XpJXETi+7JsFGK6uN6FqY0DNcE9y01+gkdBvFkHc5ArILixh2L9fpOfnCEDKp1WIBDFSGZ6dLA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -16817,9 +16817,9 @@
}
},
"node_modules/react-native-webrtc": {
"version": "118.0.3",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.3.tgz",
"integrity": "sha512-qw+aa4rxGJTvltmYwwHonx4Qcgk/tcoojONu/6y5nsXGctkUqo886EIBb29Jv4ssHnudDzvkxyG/xVKK2vJc7Q==",
"version": "118.0.2",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.2.tgz",
"integrity": "sha512-ICSv/iYXRILIHgWJXVI1ZlYkQeWZIuqi02FI+38Ch6YRbFZWUwcfhALZ5AhTRAlnUzpt+qsBxWrsq8eIG+3lTw==",
"dependencies": {
"base64-js": "1.5.1",
"debug": "4.3.4",
@@ -29183,8 +29183,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"integrity": "sha512-rtXPegsdEOx7rxQnyxoony7BXD88ssM5prGPU2Ax6AChmzW933CZu/aW7m9bP4WSFHnVvABS3M6NEF76h282Nw==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1784.0.0+639ad566/lib-jitsi-meet.tgz",
"integrity": "sha512-1K0PIItt5u88XpJXETi+7JsFGK6uN6FqY0DNcE9y01+gkdBvFkHc5ArILixh2L9fpOfnCEDKp1WIBDFSGZ6dLA==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
@@ -32118,9 +32118,9 @@
}
},
"react-native-webrtc": {
"version": "118.0.3",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.3.tgz",
"integrity": "sha512-qw+aa4rxGJTvltmYwwHonx4Qcgk/tcoojONu/6y5nsXGctkUqo886EIBb29Jv4ssHnudDzvkxyG/xVKK2vJc7Q==",
"version": "118.0.2",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-118.0.2.tgz",
"integrity": "sha512-ICSv/iYXRILIHgWJXVI1ZlYkQeWZIuqi02FI+38Ch6YRbFZWUwcfhALZ5AhTRAlnUzpt+qsBxWrsq8eIG+3lTw==",
"requires": {
"base64-js": "1.5.1",
"debug": "4.3.4",

View File

@@ -60,14 +60,14 @@
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
"i18next-browser-languagedetector": "3.0.1",
"i18next-http-backend": "2.2.1",
"i18next-http-backend": "^2.2.1",
"image-capture": "0.4.0",
"jquery": "3.6.1",
"jquery-i18next": "1.2.1",
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1790.0.0+311766e3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1784.0.0+639ad566/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -106,7 +106,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.3",
"react-native-webrtc": "118.0.2",
"react-native-webview": "13.5.1",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",

View File

@@ -133,7 +133,7 @@ dependencies {
if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../")
jsRootDir = file("../src/")
libraryName = "JitsiMeetReactNative"
codegenJavaPackageName = "org.jitsi.meet.sdk"
}

View File

@@ -0,0 +1,44 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule
extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
Activity currentActivity = getCurrentActivity();
JitsiMeetOngoingConferenceService.launch(context, currentActivity);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetOngoingConferenceService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright @ 2019-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.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetOngoingConferenceService extends Service {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
public static void launch(Context context, Activity currentActivity) {
RNOngoingNotification.createOngoingConferenceNotificationChannel(currentActivity);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
startForeground(RNOngoingNotification.NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " Service started");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

@@ -21,6 +21,7 @@ public class JitsiMeetReactNativePackage implements ReactPackage {
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new JMOngoingConferenceModule(reactContext),
new JavaScriptSandboxModule(reactContext),
new LocaleDetector(reactContext),
new LogBridgeModule(reactContext),

View File

@@ -0,0 +1,98 @@
/*
* Copyright @ 2019-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.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createOngoingConferenceNotificationChannel(Activity currentActivity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (currentActivity == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) currentActivity.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel("JitsiOngoingConferenceChannel");
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel("JitsiOngoingConferenceChannel", currentActivity.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "JitsiOngoingConferenceChannel");
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
return builder.build();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@jitsi/react-native-sdk",
"version": "2.0.2",
"version": "0.0.0",
"description": "React Native SDK for Jitsi Meet.",
"main": "index.tsx",
"license": "Apache-2.0",
@@ -11,7 +11,7 @@
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/js-utils": "2.1.3",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
@@ -20,7 +20,7 @@
"@react-navigation/native": "6.1.7",
"@react-navigation/stack": "6.3.17",
"@xmldom/xmldom": "0.8.7",
"base64-js": "1.5.1",
"base64-js": "1.3.1",
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
@@ -28,7 +28,7 @@
"i18next-http-backend": "^2.2.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1784.0.0+639ad566/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -53,14 +53,12 @@
},
"peerDependencies": {
"@amplitude/react-native": "2.7.0",
"@braintree/sanitize-url": "7.0.0",
"@giphy/react-native-sdk": "2.3.0",
"@react-native/metro-config": "0.72.9",
"@react-native-async-storage/async-storage": "1.19.4",
"@react-native-async-storage/async-storage": "1.19.3",
"@react-native-community/clipboard": "1.5.1",
"@react-native-community/netinfo": "11.1.0",
"@react-native-community/netinfo": "9.4.1",
"@react-native-community/slider": "4.4.3",
"@react-native-google-signin/google-signin": "10.1.0",
"@react-native-google-signin/google-signin": "10.0.1",
"react-native": "*",
"react": "*",
"react-native-background-timer": "2.4.1",
@@ -74,17 +72,16 @@
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-orientation-locker": "1.6.0",
"react-native-orientation-locker": "1.5.0",
"react-native-safe-area-context": "4.7.1",
"react-native-screens": "3.24.0",
"react-native-sound": "0.11.2",
"react-native-splash-screen": "3.3.0",
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "118.0.2",
"react-native-webview": "13.5.1",
"text-encoding": "0.7.0"
"react-native-webrtc": "111.0.3",
"react-native-webview": "13.5.1"
},
"overrides": {
"@xmldom/xmldom": "0.8.7"
@@ -99,4 +96,4 @@
"keywords": [
"react-native"
]
}
}

View File

@@ -79,13 +79,6 @@ function mergeDependencyVersions() {
}
}
// Updates SDK overrides dependencies.
for (const key in packageJSON.overrides) {
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {
SDKPackageJSON.overrides[key] = packageJSON.overrides[key];
}
}
const data = JSON.stringify(SDKPackageJSON, null, 4);
fs.writeFileSync('package.json', data);

View File

@@ -400,7 +400,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio
descriptionKey: errors ? 'dialog.tokenAuthFailedWithReasons' : 'dialog.tokenAuthFailed',
descriptionArguments: { reason: errors },
titleKey: 'dialog.tokenAuthFailedTitle'
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}
}

View File

@@ -596,10 +596,7 @@ function _setRoom(state: IConferenceState, action: AnyAction) {
*/
return assign(state, {
error: undefined,
localSubject: undefined,
pendingSubjectChange: undefined,
room,
subject: undefined
room
});
}

View File

@@ -1,4 +1,40 @@
import { ToolbarButton } from '../../toolbox/types';
export type ToolbarButton = 'camera' |
'chat' |
'closedcaptions' |
'desktop' |
'download' |
'embedmeeting' |
'etherpad' |
'feedback' |
'filmstrip' |
'fullscreen' |
'hangup' |
'help' |
'highlight' |
'invite' |
'linktosalesforce' |
'livestreaming' |
'microphone' |
'mute-everyone' |
'mute-video-everyone' |
'noisesuppression' |
'participants-pane' |
'profile' |
'raisehand' |
'reactions' |
'recording' |
'security' |
'select-background' |
'settings' |
'shareaudio' |
'sharedvideo' |
'shortcuts' |
'stats' |
'tileview' |
'toggle-camera' |
'videoquality' |
'whiteboard' |
'__end';
type ButtonsWithNotifyClick = 'camera' |
'chat' |
@@ -496,12 +532,10 @@ export interface IConfig {
};
recordingSharingUrl?: string;
recordings?: {
recordAudioAndVideo?: boolean;
showPrejoinWarning?: boolean;
suggestRecording?: boolean;
};
remoteVideoMenu?: {
disableDemote?: boolean;
disableGrantModerator?: boolean;
disableKick?: boolean;
disablePrivateChat?: boolean;

View File

@@ -1,3 +1,5 @@
import { ToolbarButton } from './configType';
/**
* The prefix of the {@code localStorage} key into which {@link storeConfig}
* stores and from which {@link restoreConfig} restores.
@@ -7,6 +9,50 @@
*/
export const _CONFIG_STORE_PREFIX = 'config.js';
/**
* The list of all possible UI buttons.
*
* @protected
* @type Array<string>
*/
export const TOOLBAR_BUTTONS: ToolbarButton[] = [
'camera',
'chat',
'closedcaptions',
'desktop',
'download',
'embedmeeting',
'etherpad',
'feedback',
'filmstrip',
'fullscreen',
'hangup',
'help',
'highlight',
'invite',
'linktosalesforce',
'livestreaming',
'microphone',
'mute-everyone',
'mute-video-everyone',
'participants-pane',
'profile',
'raisehand',
'recording',
'security',
'select-background',
'settings',
'shareaudio',
'noisesuppression',
'sharedvideo',
'shortcuts',
'stats',
'tileview',
'toggle-camera',
'videoquality',
'whiteboard'
];
/**
* The toolbar buttons to show on premeeting screens.
*/
@@ -17,6 +63,11 @@ export const PREMEETING_BUTTONS = [ 'microphone', 'camera', 'select-background',
*/
export const THIRD_PARTY_PREJOIN_BUTTONS = [ 'microphone', 'camera', 'select-background' ];
/**
* The toolbar buttons to show when in visitors mode.
*/
export const VISITORS_MODE_BUTTONS = [ 'chat', 'hangup', 'raisehand', 'settings', 'tileview' ];
/**
* The set of feature flags.
*

View File

@@ -7,8 +7,10 @@ import {
IDeeplinkingConfig,
IDeeplinkingDesktopConfig,
IDeeplinkingMobileConfig,
NotifyClickButton
NotifyClickButton,
ToolbarButton
} from './configType';
import { TOOLBAR_BUTTONS } from './constants';
export * from './functions.any';
@@ -32,6 +34,25 @@ export function getReplaceParticipant(state: IReduxState): string | undefined {
return state['features/base/config'].replaceParticipant;
}
/**
* Returns the list of enabled toolbar buttons.
*
* @param {Object} state - The redux state.
* @returns {Array<string>} - The list of enabled toolbar buttons.
*/
export function getToolbarButtons(state: IReduxState): Array<string> {
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
const customButtons = customToolbarButtons?.map(({ id }) => id);
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
if (customButtons) {
buttons.push(...customButtons as ToolbarButton[]);
}
return buttons;
}
/**
* Returns the configuration value of web-hid feature.
*
@@ -42,6 +63,19 @@ export function getWebHIDFeatureConfig(state: IReduxState): boolean {
return state['features/base/config'].enableWebHIDFeature || false;
}
/**
* Checks if the specified button is enabled.
*
* @param {string} buttonName - The name of the button. See {@link interfaceConfig}.
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
* @returns {boolean} - True if the button is enabled and false otherwise.
*/
export function isToolbarButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
const buttons = Array.isArray(state) ? state : getToolbarButtons(state);
return buttons.includes(buttonName);
}
/**
* Returns whether audio level measurement is enabled or not.
*

View File

@@ -1,8 +1,6 @@
import _ from 'lodash';
import { CONFERENCE_INFO } from '../../conference/components/constants';
import { TOOLBAR_BUTTONS } from '../../toolbox/constants';
import { ToolbarButton } from '../../toolbox/types';
import ReducerRegistry from '../redux/ReducerRegistry';
import { equals } from '../redux/functions';
@@ -18,8 +16,10 @@ import {
IDeeplinkingConfig,
IDeeplinkingDesktopConfig,
IDeeplinkingMobileConfig,
IMobileDynamicLink
IMobileDynamicLink,
ToolbarButton
} from './configType';
import { TOOLBAR_BUTTONS } from './constants';
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
/**

View File

@@ -52,16 +52,6 @@ export const CONNECTION_WILL_CONNECT = 'CONNECTION_WILL_CONNECT';
*/
export const SET_LOCATION_URL = 'SET_LOCATION_URL';
/**
* The type of (redux) action which sets the preferVisitor in store.
*
* {
* type: SET_PREFER_VISITOR,
* preferVisitor: ?boolean
* }
*/
export const SET_PREFER_VISITOR = 'SET_PREFER_VISITOR';
/**
* The type of (redux) action which tells whether connection info should be displayed
* on context menu.

View File

@@ -16,8 +16,7 @@ import {
CONNECTION_ESTABLISHED,
CONNECTION_FAILED,
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL,
SET_PREFER_VISITOR
SET_LOCATION_URL
} from './actionTypes';
import { JITSI_CONNECTION_URL_KEY } from './constants';
import logger from './logger';
@@ -181,22 +180,6 @@ export function setLocationURL(locationURL?: URL) {
};
}
/**
* To change prefer visitor in the store. Used later to decide what to request from jicofo on connection.
*
* @param {boolean} preferVisitor - The value to set.
* @returns {{
* type: SET_PREFER_VISITOR,
* preferVisitor: boolean
* }}
*/
export function setPreferVisitor(preferVisitor: boolean) {
return {
type: SET_PREFER_VISITOR,
preferVisitor
};
}
/**
* Opens new connection.
*

View File

@@ -10,7 +10,6 @@ import {
CONNECTION_FAILED,
CONNECTION_WILL_CONNECT,
SET_LOCATION_URL,
SET_PREFER_VISITOR,
SHOW_CONNECTION_INFO
} from './actionTypes';
import { ConnectionFailedError } from './types';
@@ -58,11 +57,6 @@ ReducerRegistry.register<IConnectionState>(
case SET_LOCATION_URL:
return _setLocationURL(state, action);
case SET_PREFER_VISITOR:
return assign(state, {
preferVisitor: action.preferVisitor
});
case SET_ROOM:
return _setRoom(state);

View File

@@ -7,9 +7,9 @@ import { IReduxState } from '../../../../app/types';
import DeviceStatus from '../../../../prejoin/components/web/preview/DeviceStatus';
import { isRoomNameEnabled } from '../../../../prejoin/functions';
import Toolbox from '../../../../toolbox/components/web/Toolbox';
import { isButtonEnabled } from '../../../../toolbox/functions.web';
import { getConferenceName } from '../../../conference/functions';
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
import { getToolbarButtons, isToolbarButtonEnabled } from '../../../config/functions.web';
import { withPixelLineHeight } from '../../../styles/functions.web';
import ConnectionStatus from './ConnectionStatus';
@@ -229,7 +229,7 @@ const PreMeetingScreen = ({
*/
function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
const { hiddenPremeetingButtons } = state['features/base/config'];
const { toolbarButtons } = state['features/toolbox'];
const toolbarButtons = getToolbarButtons(state);
const premeetingButtons = (ownProps.thirdParty
? THIRD_PARTY_PREJOIN_BUTTONS
: PREMEETING_BUTTONS).filter((b: any) => !(hiddenPremeetingButtons || []).includes(b));
@@ -244,7 +244,7 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
// toolbarButtons config overwrite.
_buttons: hiddenPremeetingButtons
? premeetingButtons
: premeetingButtons.filter(b => isButtonEnabled(b, toolbarButtons)),
: premeetingButtons.filter(b => isToolbarButtonEnabled(b, toolbarButtons)),
_premeetingBackground: premeetingBackground,
_roomName: isRoomNameEnabled(state) ? getConferenceName(state) : ''
};

View File

@@ -9,6 +9,7 @@ import { withStyles } from 'tss-react/mui';
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { getToolbarButtons } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
@@ -883,11 +884,11 @@ class Filmstrip extends PureComponent <IProps, IState> {
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const { _hasScroll = false, filmstripType, _topPanelFilmstrip, _remoteParticipants } = ownProps;
const { toolbarButtons } = state['features/toolbox'];
const toolbarButtons = getToolbarButtons(state);
const { iAmRecorder } = state['features/base/config'];
const { topPanelHeight, topPanelVisible, visible, width: verticalFilmstripWidth } = state['features/filmstrip'];
const { localScreenShare } = state['features/base/participants'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons?.length;
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const remoteVideosVisible = shouldRemoteVideosBeVisible(state);
const { isOpen: shiftRight } = state['features/chat'];
const disableSelfView = getHideSelfView(state);

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getToolbarButtons } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { LAYOUTS } from '../../../video-layout/constants';
import { getCurrentLayout } from '../../../video-layout/functions.web';
@@ -106,9 +107,9 @@ const MainFilmstrip = (props: IProps) => (
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { toolbarButtons } = state['features/toolbox'];
const toolbarButtons = getToolbarButtons(state);
const { remoteParticipants, width: verticalFilmstripWidth } = state['features/filmstrip'];
const reduceHeight = state['features/toolbox'].visible && toolbarButtons?.length;
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const {
gridDimensions: dimensions = { columns: undefined,
rows: undefined },

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { getToolbarButtons } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { LAYOUTS, LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
import { getCurrentLayout } from '../../../video-layout/functions.web';
@@ -107,9 +108,9 @@ const StageFilmstrip = (props: IProps) =>
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, _ownProps: any) {
const { toolbarButtons } = state['features/toolbox'];
const toolbarButtons = getToolbarButtons(state);
const activeParticipants = getActiveParticipantsIds(state);
const reduceHeight = state['features/toolbox'].visible && toolbarButtons?.length;
const reduceHeight = state['features/toolbox'].visible && toolbarButtons.length;
const {
gridDimensions: dimensions = { columns: undefined,
rows: undefined },

View File

@@ -116,7 +116,9 @@ export function admitMultiple(participants: Array<IKnockingParticipant>) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const conference = getCurrentConference(getState);
conference?.lobbyApproveAccess(participants.map(p => p.id));
participants.forEach(p => {
conference?.lobbyApproveAccess(p.id);
});
};
}

View File

@@ -1,3 +1,5 @@
import { NativeModules, Platform } from 'react-native';
import { getAppProp } from '../../base/app/functions';
import {
CONFERENCE_BLURRED,
@@ -9,6 +11,7 @@ import {
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT } from '../../base/participants/actionTypes';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
import { READY_TO_CLOSE } from '../external-api/actionTypes';
import { participantToParticipantInfo } from '../external-api/functions';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
@@ -16,6 +19,7 @@ import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
import { isExternalAPIAvailable } from './functions';
const externalAPIEnabled = isExternalAPIAvailable();
const { JMOngoingConference } = NativeModules;
/**
@@ -74,3 +78,21 @@ const externalAPIEnabled = isExternalAPIAvailable();
return result;
});
/**
* Before enabling media projection service control on Android,
* we need to check if native modules are being used or not.
*/
Platform.OS === 'android' && !externalAPIEnabled && StateListenerRegistry.register(
state => state['features/base/conference'].conference,
(conference, previousConference) => {
if (!conference) {
JMOngoingConference.abort();
} else if (conference && !previousConference) {
JMOngoingConference.launch();
} else if (conference !== previousConference) {
JMOngoingConference.abort();
JMOngoingConference.launch();
}
}
);

View File

@@ -15,7 +15,7 @@ import {
isParticipantAudioMuted,
isParticipantVideoMuted
} from '../../../base/tracks/functions.native';
import { showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
import { showConnectionStatus, showContextMenuDetails, showSharedVideoMenu } from '../../actions.native';
import type { MediaState } from '../../constants';
import { getParticipantAudioMediaState, getParticipantVideoMediaState } from '../../functions';
@@ -117,7 +117,11 @@ class MeetingParticipantItem extends PureComponent<IProps> {
if (_fakeParticipant && _localVideoOwner) {
dispatch(showSharedVideoMenu(_participantID));
} else if (!_fakeParticipant) {
dispatch(showContextMenuDetails(_participantID, _local));
if (_local) {
dispatch(showConnectionStatus(_participantID));
} else {
dispatch(showContextMenuDetails(_participantID));
}
} // else no-op
}

View File

@@ -6,6 +6,7 @@ import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import { rejectParticipantAudio, rejectParticipantVideo } from '../../../av-moderation/actions';
import participantsPaneTheme from '../../../base/components/themes/participantsPaneTheme.json';
import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
import { MEDIA_TYPE } from '../../../base/media/constants';
import { getParticipantById, isScreenShareParticipant } from '../../../base/participants/functions';
import { withPixelLineHeight } from '../../../base/styles/functions.web';
@@ -13,7 +14,7 @@ import Input from '../../../base/ui/components/web/Input';
import useContextMenu from '../../../base/ui/hooks/useContextMenu.web';
import { normalizeAccents } from '../../../base/util/strings.web';
import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
import { isButtonEnabled, showOverflowDrawer } from '../../../toolbox/functions.web';
import { showOverflowDrawer } from '../../../toolbox/functions.web';
import { muteRemote } from '../../../video-menu/actions.web';
import { getSortedParticipantIds, isCurrentRoomRenamable, shouldRenderInviteButton } from '../../functions';
import { useParticipantDrawer } from '../../hooks';
@@ -183,7 +184,7 @@ function _mapStateToProps(state: IReduxState) {
});
const participantsCount = sortedParticipantIds.length;
const showInviteButton = shouldRenderInviteButton(state) && isButtonEnabled('invite', state);
const showInviteButton = shouldRenderInviteButton(state) && isToolbarButtonEnabled('invite', state);
const overflowDrawer = showOverflowDrawer(state);
const currentRoomId = getCurrentRoomId(state);
const currentRoom = getBreakoutRooms(state)[currentRoomId];

View File

@@ -1,6 +1,7 @@
import { IReduxState } from '../app/types';
import { getToolbarButtons } from '../base/config/functions.web';
import { isReactionsEnabled } from './functions.any';
import { shouldDisplayReactionsButtons } from './functions.any';
export * from './functions.any';
@@ -21,7 +22,5 @@ export function getReactionsMenuVisibility(state: IReduxState): boolean {
* @returns {boolean}
*/
export function isReactionsButtonEnabled(state: IReduxState) {
const { toolbarButtons } = state['features/toolbox'];
return Boolean(toolbarButtons?.includes('reactions')) && isReactionsEnabled(state);
return Boolean(getToolbarButtons(state).includes('reactions')) && shouldDisplayReactionsButtons(state);
}

View File

@@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { getToolbarButtons } from '../../../../base/config/functions.web';
import { openDialog } from '../../../../base/dialog/actions';
import { translate } from '../../../../base/i18n/functions';
import AbstractLiveStreamButton, {
@@ -49,11 +50,11 @@ class LiveStreamButton extends AbstractLiveStreamButton<IProps> {
*/
function _mapStateToProps(state: IReduxState, ownProps: any) {
const abstractProps = _abstractMapStateToProps(state, ownProps);
const { toolbarButtons } = state['features/toolbox'];
const toolbarButtons = getToolbarButtons(state);
let { visible } = ownProps;
if (typeof visible === 'undefined') {
visible = Boolean(toolbarButtons?.includes('livestreaming') && abstractProps.visible);
visible = toolbarButtons.includes('livestreaming') && abstractProps.visible;
}
return {

View File

@@ -64,11 +64,6 @@ export interface IProps extends WithTranslation {
*/
_rToken: string;
/**
* Whether the record audio / video option is enabled by default.
*/
_recordAudioAndVideo: boolean;
/**
* Whether or not the local participant is screensharing.
*/
@@ -192,7 +187,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
isValidating: false,
userName: undefined,
sharingEnabled: true,
shouldRecordAudioAndVideo: this.props._recordAudioAndVideo,
shouldRecordAudioAndVideo: true,
shouldRecordTranscription: this.props._autoTranscribeOnRecord,
spaceLeft: undefined,
selectedRecordingService,
@@ -457,8 +452,7 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
const {
recordingService,
dropbox = { appKey: undefined },
localRecording,
recordings = { recordAudioAndVideo: true }
localRecording
} = state['features/base/config'];
const {
_displaySubtitles,
@@ -475,7 +469,6 @@ export function mapStateToProps(state: IReduxState, _ownProps: any) {
_isDropboxEnabled: isDropboxEnabled(state),
_localRecordingEnabled: !localRecording?.disable,
_rToken: state['features/dropbox'].rToken ?? '',
_recordAudioAndVideo: recordings?.recordAudioAndVideo ?? true,
_subtitlesLanguage,
_tokenExpireDate: state['features/dropbox'].expireDate,
_token: state['features/dropbox'].token ?? ''

View File

@@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import { getToolbarButtons } from '../../../../base/config/functions.web';
import { openDialog } from '../../../../base/dialog/actions';
import { translate } from '../../../../base/i18n/functions';
import AbstractRecordButton, {
@@ -48,8 +49,8 @@ class RecordingButton extends AbstractRecordButton<IProps> {
*/
export function _mapStateToProps(state: IReduxState) {
const abstractProps = _abstractMapStateToProps(state);
const { toolbarButtons } = state['features/toolbox'];
const visible = Boolean(toolbarButtons?.includes('recording') && abstractProps.visible);
const toolbarButtons = getToolbarButtons(state);
const visible = toolbarButtons.includes('recording') && abstractProps.visible;
return {
...abstractProps,

View File

@@ -253,12 +253,12 @@ export function getRecordButtonProps(state: IReduxState) {
const localRecordingEnabled = !localRecording?.disable && supportsLocalRecording();
const dropboxEnabled = isDropboxEnabled(state);
const recordingEnabled = recordingService?.enabled || dropboxEnabled;
const recordingEnabled = recordingService?.enabled || localRecordingEnabled || dropboxEnabled;
if (localRecordingEnabled) {
visible = true;
} else if (isModerator) {
if (isModerator) {
visible = recordingEnabled ? isJwtFeatureEnabled(state, 'recording', true) : false;
} else {
visible = navigator.product !== 'ReactNative' && localRecordingEnabled;
}
// disable the button if the livestreaming is running.

View File

@@ -54,17 +54,6 @@ export const SET_OVERFLOW_DRAWER = 'SET_OVERFLOW_DRAWER';
*/
export const SET_OVERFLOW_MENU_VISIBLE = 'SET_OVERFLOW_MENU_VISIBLE';
/**
* The type of the action which sets enabled toolbar buttons.
*
* {
* type: SET_TOOLBAR_BUTTONS,
* toolbarButtons: Array<string>
* }
*/
export const SET_TOOLBAR_BUTTONS = 'SET_TOOLBAR_BUTTONS';
/**
* The type of the action which sets the indicator which determines whether a
* fToolbar in the Toolbox is hovered.

View File

@@ -5,12 +5,19 @@ import { makeStyles } from 'tss-react/mui';
import { IReduxState, IStore } from '../../../app/types';
import { NotifyClickButton } from '../../../base/config/configType';
import { getButtonNotifyMode, getButtonsWithNotifyClick } from '../../../base/config/functions.web';
import { VISITORS_MODE_BUTTONS } from '../../../base/config/constants';
import {
getButtonNotifyMode,
getButtonsWithNotifyClick,
getToolbarButtons,
isToolbarButtonEnabled
} from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import { isLocalParticipantModerator } from '../../../base/participants/functions';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import { isReactionsButtonEnabled, shouldDisplayReactionsButtons } from '../../../reactions/functions.web';
import { iAmVisitor } from '../../../visitors/functions';
import {
setHangupMenuVisible,
setOverflowMenuVisible,
@@ -21,7 +28,6 @@ import { NOT_APPLICABLE, THRESHOLDS } from '../../constants';
import {
getAllToolboxButtons,
getJwtDisabledButtons,
isButtonEnabled,
isToolboxVisible
} from '../../functions.web';
import { useKeyboardShortcuts } from '../../hooks.web';
@@ -283,7 +289,7 @@ const Toolbox = ({
const buttons = getAllToolboxButtons(_customToolbarButtons);
setButtonsNotifyClickMode(buttons);
const isHangupVisible = isButtonEnabled('hangup', _toolbarButtons);
const isHangupVisible = isToolbarButtonEnabled('hangup', _toolbarButtons);
const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|| THRESHOLDS[THRESHOLDS.length - 1];
@@ -294,7 +300,7 @@ const Toolbox = ({
...Object.values(buttons).filter((button, index) => !order.includes(keys[index]))
].filter(({ key, alias = NOT_APPLICABLE }) =>
!_jwtDisabledButtons.includes(key)
&& (isButtonEnabled(key, _toolbarButtons) || isButtonEnabled(alias, _toolbarButtons))
&& (isToolbarButtonEnabled(key, _toolbarButtons) || isToolbarButtonEnabled(alias, _toolbarButtons))
);
let sliceIndex = _overflowDrawer || _reactionsButtonEnabled ? order.length + 2 : order.length + 1;
@@ -417,7 +423,7 @@ const Toolbox = ({
showReactionsMenu = { showReactionsInOverflowMenu } />
)}
{isButtonEnabled('hangup', _toolbarButtons) && (
{isToolbarButtonEnabled('hangup', _toolbarButtons) && (
_endConferenceSupported
? <HangupMenuButton
ariaControls = 'hangup-menu'
@@ -447,7 +453,7 @@ const Toolbox = ({
customClass = 'hangup-button'
key = 'hangup-button'
notifyMode = { getButtonNotifyMode('hangup', _buttonsWithNotifyClick) }
visible = { isButtonEnabled('hangup', _toolbarButtons) } />
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
)}
</div>
</div>
@@ -496,7 +502,11 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
overflowDrawer
} = state['features/toolbox'];
const { clientWidth } = state['features/base/responsive-ui'];
const toolbarButtons = ownProps.toolbarButtons || state['features/toolbox'].toolbarButtons;
let toolbarButtons = ownProps.toolbarButtons || getToolbarButtons(state);
if (iAmVisitor(state)) {
toolbarButtons = VISITORS_MODE_BUTTONS.filter(e => toolbarButtons.indexOf(e) > -1);
}
return {
_buttonsWithNotifyClick: getButtonsWithNotifyClick(state),

View File

@@ -1,5 +1,3 @@
import { ToolbarButton } from './types';
/**
* Thresholds for displaying toolbox buttons.
*/
@@ -52,62 +50,3 @@ export const ZINDEX_DIALOG_PORTAL = 302;
* Color for spinner displayed in the toolbar.
*/
export const SPINNER_COLOR = '#929292';
/**
* The list of all possible UI buttons.
*
* @protected
* @type Array<string>
*/
export const TOOLBAR_BUTTONS: ToolbarButton[] = [
'camera',
'chat',
'closedcaptions',
'desktop',
'download',
'embedmeeting',
'etherpad',
'feedback',
'filmstrip',
'fullscreen',
'hangup',
'help',
'highlight',
'invite',
'linktosalesforce',
'livestreaming',
'microphone',
'mute-everyone',
'mute-video-everyone',
'participants-pane',
'profile',
'raisehand',
'recording',
'security',
'select-background',
'settings',
'shareaudio',
'noisesuppression',
'sharedvideo',
'shortcuts',
'stats',
'tileview',
'toggle-camera',
'videoquality',
'whiteboard'
];
/**
* The toolbar buttons to show when in visitors mode.
*/
export const VISITORS_MODE_BUTTONS: ToolbarButton[] = [
'chat',
'hangup',
'raisehand',
'settings',
'tileview',
'fullscreen',
'stats',
'videoquality'
];

View File

@@ -1,4 +1,5 @@
import { IReduxState } from '../app/types';
import { getToolbarButtons } from '../base/config/functions.web';
import { hasAvailableDevices } from '../base/devices/functions';
import { MEET_FEATURES } from '../base/jwt/constants';
import { isJwtFeatureEnabled } from '../base/jwt/functions';
@@ -55,16 +56,18 @@ export function getToolboxHeight() {
}
/**
* Checks if the specified button is enabled.
* Indicates if a toolbar button is enabled.
*
* @param {string} buttonName - The name of the button. See {@link interfaceConfig}.
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
* @returns {boolean} - True if the button is enabled and false otherwise.
* @param {string} name - The name of the setting section as defined in
* interface_config.js.
* @param {IReduxState} state - The redux state.
* @returns {boolean|undefined} - True to indicate that the given toolbar button
* is enabled, false - otherwise.
*/
export function isButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || [];
export function isButtonEnabled(name: string, state: IReduxState) {
const toolbarButtons = getToolbarButtons(state);
return buttons.includes(buttonName);
return toolbarButtons.indexOf(name) !== -1;
}
/**

View File

@@ -4,6 +4,7 @@ import { batch, useDispatch, useSelector } from 'react-redux';
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IReduxState } from '../app/types';
import { getToolbarButtons, isToolbarButtonEnabled } from '../base/config/functions.web';
import { toggleDialog } from '../base/dialog/actions';
import JitsiMeetJS from '../base/lib-jitsi-meet';
import { raiseHand } from '../base/participants/actions';
@@ -33,15 +34,14 @@ import { shouldDisplayTileView } from '../video-layout/functions.any';
import VideoQualityDialog from '../video-quality/components/VideoQualityDialog.web';
import { setFullScreen } from './actions.web';
import { isButtonEnabled, isDesktopShareButtonDisabled } from './functions.web';
import { isDesktopShareButtonDisabled } from './functions.web';
export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
const dispatch = useDispatch();
const _isSpeakerStatsDisabled = useSelector(isSpeakerStatsDisabled);
const _isParticipantsPaneEnabled = useSelector(isParticipantsPaneEnabled);
const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons);
const _toolbarButtons = useSelector(
(state: IReduxState) => toolbarButtons || state['features/toolbox'].toolbarButtons);
const _toolbarButtons = useSelector((state: IReduxState) => toolbarButtons || getToolbarButtons(state));
const chatOpen = useSelector((state: IReduxState) => state['features/chat'].isOpen);
const desktopSharingButtonDisabled = useSelector(isDesktopShareButtonDisabled);
const desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
@@ -205,42 +205,42 @@ export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
useEffect(() => {
const KEYBOARD_SHORTCUTS = [
isButtonEnabled('videoquality', _toolbarButtons) && {
isToolbarButtonEnabled('videoquality', _toolbarButtons) && {
character: 'A',
exec: onToggleVideoQuality,
helpDescription: 'toolbar.callQuality'
},
isButtonEnabled('chat', _toolbarButtons) && {
isToolbarButtonEnabled('chat', _toolbarButtons) && {
character: 'C',
exec: onToggleChat,
helpDescription: 'keyboardShortcuts.toggleChat'
},
isButtonEnabled('desktop', _toolbarButtons) && {
isToolbarButtonEnabled('desktop', _toolbarButtons) && {
character: 'D',
exec: onToggleScreenshare,
helpDescription: 'keyboardShortcuts.toggleScreensharing'
},
_isParticipantsPaneEnabled && isButtonEnabled('participants-pane', _toolbarButtons) && {
_isParticipantsPaneEnabled && isToolbarButtonEnabled('participants-pane', _toolbarButtons) && {
character: 'P',
exec: onToggleParticipantsPane,
helpDescription: 'keyboardShortcuts.toggleParticipantsPane'
},
isButtonEnabled('raisehand', _toolbarButtons) && {
isToolbarButtonEnabled('raisehand', _toolbarButtons) && {
character: 'R',
exec: onToggleRaiseHand,
helpDescription: 'keyboardShortcuts.raiseHand'
},
isButtonEnabled('fullscreen', _toolbarButtons) && {
isToolbarButtonEnabled('fullscreen', _toolbarButtons) && {
character: 'S',
exec: onToggleFullScreen,
helpDescription: 'keyboardShortcuts.fullScreen'
},
isButtonEnabled('tileview', _toolbarButtons) && {
isToolbarButtonEnabled('tileview', _toolbarButtons) && {
character: 'W',
exec: onToggleTileView,
helpDescription: 'toolbar.tileViewToggle'
},
!_isSpeakerStatsDisabled && isButtonEnabled('stats', _toolbarButtons) && {
!_isSpeakerStatsDisabled && isToolbarButtonEnabled('stats', _toolbarButtons) && {
character: 'T',
exec: onSpeakerStats,
helpDescription: 'keyboardShortcuts.showSpeakerStats'

View File

@@ -1,18 +1,12 @@
import { AnyAction } from 'redux';
import { IReduxState } from '../app/types';
import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { I_AM_VISITOR_MODE } from '../visitors/actionTypes';
import { iAmVisitor } from '../visitors/functions';
import {
CLEAR_TOOLBOX_TIMEOUT,
SET_FULL_SCREEN,
SET_TOOLBAR_BUTTONS,
SET_TOOLBOX_TIMEOUT
} from './actionTypes';
import { TOOLBAR_BUTTONS, VISITORS_MODE_BUTTONS } from './constants';
import './subscriber.web';
@@ -31,20 +25,6 @@ MiddlewareRegistry.register(store => next => action => {
clearTimeout(timeoutID ?? undefined);
break;
}
case UPDATE_CONFIG:
case OVERWRITE_CONFIG:
case I_AM_VISITOR_MODE:
case SET_CONFIG: {
const result = next(action);
const toolbarButtons = _getToolbarButtons(store.getState());
store.dispatch({
type: SET_TOOLBAR_BUTTONS,
toolbarButtons
});
return result;
}
case SET_FULL_SCREEN:
return _setFullScreen(next, action);
@@ -105,25 +85,3 @@ function _setFullScreen(next: Function, action: AnyAction) {
return result;
}
/**
* Returns the list of enabled toolbar buttons.
*
* @param {Object} state - The redux state.
* @returns {Array<string>} - The list of enabled toolbar buttons.
*/
function _getToolbarButtons(state: IReduxState): Array<string> {
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
const customButtons = customToolbarButtons?.map(({ id }) => id);
let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
if (iAmVisitor(state)) {
buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1);
}
if (customButtons) {
return [ ...buttons, ...customButtons ];
}
return buttons;
}

View File

@@ -7,7 +7,6 @@ import {
SET_HANGUP_MENU_VISIBLE,
SET_OVERFLOW_DRAWER,
SET_OVERFLOW_MENU_VISIBLE,
SET_TOOLBAR_BUTTONS,
SET_TOOLBAR_HOVERED,
SET_TOOLBOX_ENABLED,
SET_TOOLBOX_SHIFT_UP,
@@ -70,13 +69,6 @@ const INITIAL_STATE = {
*/
timeoutID: null,
/**
* The list of enabled toolbar buttons.
*
* @type {Array<string>}
*/
toolbarButtons: [],
/**
* The indicator that determines whether the Toolbox is visible.
@@ -95,7 +87,6 @@ export interface IToolboxState {
overflowMenuVisible: boolean;
shiftUp: boolean;
timeoutID?: number | null;
toolbarButtons: Array<string>;
visible: boolean;
}
@@ -133,12 +124,6 @@ ReducerRegistry.register<IToolboxState>(
overflowMenuVisible: action.visible
};
case SET_TOOLBAR_BUTTONS:
return {
...state,
toolbarButtons: action.toolbarButtons
};
case SET_TOOLBAR_HOVERED:
return {
...state,

View File

@@ -6,41 +6,3 @@ export interface IToolboxButton {
group: number;
key: string;
}
export type ToolbarButton = 'camera' |
'chat' |
'closedcaptions' |
'desktop' |
'download' |
'embedmeeting' |
'etherpad' |
'feedback' |
'filmstrip' |
'fullscreen' |
'hangup' |
'help' |
'highlight' |
'invite' |
'linktosalesforce' |
'livestreaming' |
'microphone' |
'mute-everyone' |
'mute-video-everyone' |
'noisesuppression' |
'participants-pane' |
'profile' |
'raisehand' |
'reactions' |
'recording' |
'security' |
'select-background' |
'settings' |
'shareaudio' |
'sharedvideo' |
'shortcuts' |
'stats' |
'tileview' |
'toggle-camera' |
'videoquality' |
'whiteboard' |
'__end';

View File

@@ -1,7 +1,5 @@
/* eslint-disable lines-around-comment */
// @ts-ignore
export { default as DemoteToVisitorDialog } from './native/DemoteToVisitorDialog';
// @ts-ignore
export { default as GrantModeratorDialog } from './native/GrantModeratorDialog';
// @ts-ignore
export { default as KickRemoteParticipantDialog } from './native/KickRemoteParticipantDialog';

View File

@@ -1,4 +1,3 @@
export { default as DemoteToVisitorDialog } from './web/DemoteToVisitorDialog';
export { default as GrantModeratorDialog } from './web/GrantModeratorDialog';
export { default as KickRemoteParticipantDialog } from './web/KickRemoteParticipantDialog';
export { default as MuteEveryoneDialog } from './web/MuteEveryoneDialog';

View File

@@ -1,52 +0,0 @@
import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { openDialog } from '../../../base/dialog/actions';
import { translate } from '../../../base/i18n/functions';
import { IconUsers } from '../../../base/icons/svg';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import DemoteToVisitorDialog from './DemoteToVisitorDialog';
interface IProps extends AbstractButtonProps {
/**
* The ID of the participant that this button is supposed to kick.
*/
participantID: string;
}
/**
* Implements a React {@link Component} which displays a button for demoting a participant to visitor.
*/
class DemoteToVisitorButton extends AbstractButton<IProps> {
accessibilityLabel = 'videothumbnail.demote';
icon = IconUsers;
label = 'videothumbnail.demote';
/**
* Handles clicking / pressing the button, and demoting the participant.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, participantID } = this.props;
dispatch(openDialog(DemoteToVisitorDialog, { participantID }));
}
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state: IReduxState) {
return {
visible: state['features/visitors'].supported
};
}
export default translate(connect(_mapStateToProps)(DemoteToVisitorButton));

View File

@@ -1,38 +0,0 @@
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import ConfirmDialog from '../../../base/dialog/components/native/ConfirmDialog';
import { DialogProps } from '../../../base/dialog/constants';
import { demoteRequest } from '../../../visitors/actions';
interface IProps extends DialogProps {
/**
* The ID of the remote participant to be demoted.
*/
participantID: string;
}
/**
* Dialog to confirm a remote participant demote to visitor action.
*
* @returns {JSX.Element}
*/
export default function DemoteToVisitorDialog({ participantID }: IProps): JSX.Element {
const dispatch = useDispatch();
const handleSubmit = useCallback(() => {
dispatch(demoteRequest(participantID));
return true; // close dialog
}, [ dispatch, participantID ]);
return (
<ConfirmDialog
cancelLabel = 'dialog.Cancel'
confirmLabel = 'dialog.confirm'
descriptionKey = 'dialog.demoteParticipantDialog'
isConfirmDestructive = { true }
onSubmit = { handleSubmit }
title = 'dialog.demoteParticipantTitle' />
);
}

View File

@@ -4,20 +4,17 @@ import { connect } from 'react-redux';
import { IReduxState, IStore } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { hideSheet } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { bottomSheetStyles } from '../../../base/dialog/components/native/styles';
import { translate } from '../../../base/i18n/functions';
import {
getLocalParticipant,
getParticipantCount,
getParticipantDisplayName
} from '../../../base/participants/functions';
import { ILocalParticipant } from '../../../base/participants/types';
import ToggleSelfViewButton from '../../../toolbox/components/native/ToggleSelfViewButton';
import ConnectionStatusButton from './ConnectionStatusButton';
import DemoteToVisitorButton from './DemoteToVisitorButton';
import styles from './styles';
/**
@@ -37,11 +34,6 @@ interface IProps {
*/
_participantDisplayName: string;
/**
* Shows/hides the local switch to visitor button.
*/
_showDemote: boolean;
/**
* The Redux dispatch function.
*/
@@ -65,7 +57,6 @@ class LocalVideoMenu extends PureComponent<IProps> {
constructor(props: IProps) {
super(props);
this._onCancel = this._onCancel.bind(this);
this._renderMenuHeader = this._renderMenuHeader.bind(this);
}
@@ -75,9 +66,8 @@ class LocalVideoMenu extends PureComponent<IProps> {
* @inheritdoc
*/
render() {
const { _participant, _showDemote } = this.props;
const { _participant } = this.props;
const buttonProps = {
afterClick: this._onCancel,
showLabel: true,
participantID: _participant?.id ?? '',
styles: bottomSheetStyles.buttons
@@ -88,7 +78,6 @@ class LocalVideoMenu extends PureComponent<IProps> {
renderHeader = { this._renderMenuHeader }
showSlidingView = { true }>
<ToggleSelfViewButton { ...buttonProps } />
{ _showDemote && <DemoteToVisitorButton { ...buttonProps } /> }
<ConnectionStatusButton { ...buttonProps } />
</BottomSheet>
);
@@ -116,16 +105,6 @@ class LocalVideoMenu extends PureComponent<IProps> {
</View>
);
}
/**
* Callback to hide the {@code RemoteVideoMenu}.
*
* @private
* @returns {boolean}
*/
_onCancel() {
this.props.dispatch(hideSheet());
}
}
/**
@@ -140,8 +119,7 @@ function _mapStateToProps(state: IReduxState) {
return {
_participant: participant,
_participantDisplayName: getParticipantDisplayName(state, participant?.id ?? ''),
_showDemote: getParticipantCount(state) > 1
_participantDisplayName: getParticipantDisplayName(state, participant?.id ?? '')
};
}

View File

@@ -24,7 +24,6 @@ import PrivateMessageButton from '../../../chat/components/native/PrivateMessage
import AskUnmuteButton from './AskUnmuteButton';
import ConnectionStatusButton from './ConnectionStatusButton';
import DemoteToVisitorButton from './DemoteToVisitorButton';
import GrantModeratorButton from './GrantModeratorButton';
import KickButton from './KickButton';
import MuteButton from './MuteButton';
@@ -93,11 +92,6 @@ interface IProps {
*/
_rooms: Array<IRoom>;
/**
* Whether to display the demote button.
*/
_showDemote: boolean;
/**
* The Redux dispatch function.
*/
@@ -145,7 +139,6 @@ class RemoteVideoMenu extends PureComponent<IProps> {
_isParticipantAvailable,
_moderator,
_rooms,
_showDemote,
_currentRoomId,
participantId,
t
@@ -175,7 +168,6 @@ class RemoteVideoMenu extends PureComponent<IProps> {
{ !_disableKick && <KickButton { ...buttonProps } /> }
{ !_disableGrantModerator && !_isBreakoutRoom && <GrantModeratorButton { ...buttonProps } /> }
<PinButton { ...buttonProps } />
{ _showDemote && <DemoteToVisitorButton { ...buttonProps } /> }
{ !_disablePrivateChat && <PrivateMessageButton { ...buttonProps } /> }
<ConnectionStatusButton { ...connectionStatusButtonProps } />
{_moderator && _rooms.length > 1 && <>
@@ -260,8 +252,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_isParticipantAvailable: Boolean(isParticipantAvailable),
_moderator: moderator,
_participantDisplayName: getParticipantDisplayName(state, participantId),
_rooms,
_showDemote: moderator
_rooms
};
}

View File

@@ -1,63 +0,0 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { openDialog } from '../../../base/dialog/actions';
import { IconUsers } from '../../../base/icons/svg';
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants';
import { IButtonProps } from '../../types';
import DemoteToVisitorDialog from './DemoteToVisitorDialog';
interface IProps extends IButtonProps {
/**
* Button text class name.
*/
className?: string;
/**
* Whether the icon should be hidden or not.
*/
noIcon?: boolean;
/**
* Click handler executed aside from the main action.
*/
onClick?: Function;
}
/**
* Implements a React {@link Component} which displays a button for demoting a participant to visitor.
*
* @returns {JSX.Element}
*/
export default function DemoteToVisitorButton({
className,
noIcon = false,
notifyClick,
notifyMode,
participantID
}: IProps): JSX.Element {
const { t } = useTranslation();
const dispatch = useDispatch();
const handleClick = useCallback(() => {
notifyClick?.();
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
return;
}
dispatch(openDialog(DemoteToVisitorDialog, { participantID }));
}, [ dispatch, notifyClick, notifyMode, participantID ]);
return (
<ContextMenuItem
accessibilityLabel = { t('videothumbnail.demote') }
className = { className || 'demotelink' } // can be used in tests
icon = { noIcon ? null : IconUsers }
id = { `demotelink_${participantID}` }
onClick = { handleClick }
text = { t('videothumbnail.demote') } />
);
}

View File

@@ -1,40 +0,0 @@
import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { DialogProps } from '../../../base/dialog/constants';
import Dialog from '../../../base/ui/components/web/Dialog';
import { demoteRequest } from '../../../visitors/actions';
interface IProps extends DialogProps {
/**
* The ID of the remote participant to be demoted.
*/
participantID: string;
}
/**
* Dialog to confirm a remote participant demote action.
*
* @returns {JSX.Element}
*/
export default function DemoteToVisitorDialog({ participantID }: IProps): JSX.Element {
const { t } = useTranslation();
const dispatch = useDispatch();
const handleSubmit = useCallback(() => {
dispatch(demoteRequest(participantID));
}, [ dispatch, participantID ]);
return (
<Dialog
ok = {{ translationKey: 'dialog.confirm' }}
onSubmit = { handleSubmit }
titleKey = 'dialog.demoteParticipantTitle'>
<div>
{ t('dialog.demoteParticipantDialog') }
</div>
</Dialog>
);
}

View File

@@ -109,7 +109,7 @@ class HideSelfViewVideoButton extends PureComponent<IProps> {
}
/**
* Maps (parts of) the Redux state to the associated {@code HideSelfViewVideoButton}'s props.
* Maps (parts of) the Redux state to the associated {@code FlipLocalVideoButton}'s props.
*
* @param {Object} state - The Redux state.
* @private

View File

@@ -7,7 +7,7 @@ import { IReduxState, IStore } from '../../../app/types';
import { getButtonNotifyMode, getParticipantMenuButtonsWithNotifyClick } from '../../../base/config/functions.web';
import { isMobileBrowser } from '../../../base/environment/utils';
import { IconDotsHorizontal } from '../../../base/icons/svg';
import { getLocalParticipant, getParticipantCount } from '../../../base/participants/functions';
import { getLocalParticipant } from '../../../base/participants/functions';
import Popover from '../../../base/popover/components/Popover.web';
import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
import { getHideSelfView } from '../../../base/settings/functions.web';
@@ -23,7 +23,6 @@ import { renderConnectionStatus } from '../../actions.web';
import { PARTICIPANT_MENU_BUTTONS as BUTTONS } from '../../constants';
import ConnectionStatusButton from './ConnectionStatusButton';
import DemoteToVisitorButton from './DemoteToVisitorButton';
import FlipLocalVideoButton from './FlipLocalVideoButton';
import HideSelfViewVideoButton from './HideSelfViewVideoButton';
import TogglePinToStageButton from './TogglePinToStageButton';
@@ -55,11 +54,6 @@ interface IProps {
*/
_showConnectionInfo: boolean;
/**
* Shows/hides the local switch to visitor button.
*/
_showDemote: boolean;
/**
* Whether to render the hide self view button.
*/
@@ -137,7 +131,6 @@ const LocalVideoMenuTriggerButton = ({
_menuPosition,
_overflowDrawer,
_showConnectionInfo,
_showDemote,
_showHideSelfViewButton,
_showLocalVideoFlipButton,
_showPinToStage,
@@ -150,7 +143,6 @@ const LocalVideoMenuTriggerButton = ({
const { classes } = useStyles();
const { t } = useTranslation();
const buttonsWithNotifyClick = useSelector(getParticipantMenuButtonsWithNotifyClick);
const visitorsSupported = useSelector((state: IReduxState) => state['features/visitors'].supported);
const notifyClick = useCallback(
(buttonKey: string) => {
@@ -214,16 +206,6 @@ const LocalVideoMenuTriggerButton = ({
onClick = { hidePopover }
participantID = { _localParticipantId } />
}
{
_showDemote && visitorsSupported && <DemoteToVisitorButton
className = { _overflowDrawer ? classes.flipText : '' }
noIcon = { true }
// eslint-disable-next-line react/jsx-no-bind
notifyClick = { () => notifyClick(BUTTONS.DEMOTE) }
notifyMode = { getButtonNotifyMode(BUTTONS.DEMOTE, buttonsWithNotifyClick) }
onClick = { hidePopover }
participantID = { _localParticipantId } />
}
{
isMobileBrowser() && <ConnectionStatusButton
// eslint-disable-next-line react/jsx-no-bind
@@ -292,7 +274,6 @@ function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
return {
_menuPosition,
_showDemote: getParticipantCount(state) > 1,
_showLocalVideoFlipButton: !disableLocalVideoFlip && videoTrack?.videoType !== 'desktop',
_showHideSelfViewButton: showHideSelfViewButton,
_overflowDrawer: overflowDrawer,

View File

@@ -31,7 +31,6 @@ import { PARTICIPANT_MENU_BUTTONS as BUTTONS } from '../../constants';
import AskToUnmuteButton from './AskToUnmuteButton';
import ConnectionStatusButton from './ConnectionStatusButton';
import CustomOptionButton from './CustomOptionButton';
import DemoteToVisitorButton from './DemoteToVisitorButton';
import GrantModeratorButton from './GrantModeratorButton';
import KickButton from './KickButton';
import MuteButton from './MuteButton';
@@ -142,8 +141,7 @@ const ParticipantContextMenu = ({
const { remoteVideoMenu = {}, disableRemoteMute, startSilent, customParticipantMenuButtons }
= useSelector((state: IReduxState) => state['features/base/config']);
const visitorsMode = useSelector((state: IReduxState) => iAmVisitor(state));
const visitorsSupported = useSelector((state: IReduxState) => state['features/visitors'].supported);
const { disableDemote, disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu;
const { disableKick, disableGrantModerator, disablePrivateChat } = remoteVideoMenu;
const { participantsVolume } = useSelector((state: IReduxState) => state['features/filmstrip']);
const _volume = (participant?.local ?? true ? undefined
: participant?.id ? participantsVolume[participant?.id] : undefined) ?? 1;
@@ -247,10 +245,6 @@ const ParticipantContextMenu = ({
buttons2.push(<GrantModeratorButton { ...getButtonProps(BUTTONS.GRANT_MODERATOR) } />);
}
if (!disableDemote && visitorsSupported && _isModerator) {
buttons2.push(<DemoteToVisitorButton { ...getButtonProps(BUTTONS.DEMOTE) } />);
}
if (!disableKick) {
buttons2.push(<KickButton { ...getButtonProps(BUTTONS.KICK) } />);
}

View File

@@ -20,7 +20,6 @@ export const PARTICIPANT_MENU_BUTTONS = {
ALLOW_VIDEO: 'allow-video',
ASK_UNMUTE: 'ask-unmute',
CONN_STATUS: 'conn-status',
DEMOTE: 'demote',
FLIP_LOCAL_VIDEO: 'flip-local-video',
GRANT_MODERATOR: 'grant-moderator',
HIDE_SELF_VIEW: 'hide-self-view',

View File

@@ -38,23 +38,3 @@ export const VISITOR_PROMOTION_REQUEST = 'VISITOR_PROMOTION_REQUEST';
* }
*/
export const CLEAR_VISITOR_PROMOTION_REQUEST = 'CLEAR_VISITOR_PROMOTION_REQUEST';
/**
* The type of (redux) action which sets visitor demote actor.
*
* {
* type: SET_VISITOR_DEMOTE_ACTOR,
* displayName: string
* }
*/
export const SET_VISITOR_DEMOTE_ACTOR = 'SET_VISITOR_DEMOTE_ACTOR';
/**
* The type of (redux) action which sets visitors support.
*
* {
* type: SET_VISITORS_SUPPORTED,
* value: string
* }
*/
export const SET_VISITORS_SUPPORTED = 'SET_VISITORS_SUPPORTED';

View File

@@ -1,15 +1,9 @@
import { createRemoteVideoMenuButtonEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
import { IStore } from '../app/types';
import { getCurrentConference } from '../base/conference/functions';
import { connect, disconnect, setPreferVisitor } from '../base/connection/actions';
import { getLocalParticipant } from '../base/participants/functions';
import {
CLEAR_VISITOR_PROMOTION_REQUEST,
I_AM_VISITOR_MODE,
SET_VISITORS_SUPPORTED,
SET_VISITOR_DEMOTE_ACTOR,
UPDATE_VISITORS_COUNT,
VISITOR_PROMOTION_REQUEST
} from './actionTypes';
@@ -25,11 +19,13 @@ export function admitMultiple(requests: Array<IPromotionRequest>): Function {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const conference = getCurrentConference(getState);
conference?.sendMessage({
type: 'visitors',
action: 'promotion-response',
approved: true,
ids: requests.map(r => r.from)
requests.forEach(r => {
conference?.sendMessage({
type: 'visitors',
action: 'promotion-response',
approved: true,
id: r.from
});
});
};
}
@@ -76,34 +72,6 @@ export function denyRequest(request: IPromotionRequest) {
};
}
/**
* Sends a demote request to a main participant to join the meeting as a visitor.
*
* @param {string} id - The ID for the participant.
* @returns {Function}
*/
export function demoteRequest(id: string) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const conference = getCurrentConference(getState);
const localParticipant = getLocalParticipant(getState());
sendAnalytics(createRemoteVideoMenuButtonEvent('demote.button', { 'participant_id': id }));
if (id === localParticipant?.id) {
dispatch(disconnect(true))
.then(() => dispatch(setPreferVisitor(true)))
.then(() => dispatch(connect()));
} else {
conference?.sendMessage({
type: 'visitors',
action: 'demote-request',
id,
actor: localParticipant?.id
});
}
};
}
/**
* Removes a promotion request from the state.
*
@@ -150,36 +118,6 @@ export function setIAmVisitor(enabled: boolean) {
};
}
/**
* Sets visitor demote actor.
*
* @param {string|undefined} displayName - The display name of the participant.
* @returns {{
* type: SET_VISITOR_DEMOTE_ACTOR,
* }}
*/
export function setVisitorDemoteActor(displayName: string | undefined) {
return {
type: SET_VISITOR_DEMOTE_ACTOR,
displayName
};
}
/**
* Visitors count has been updated.
*
* @param {boolean} value - The new value whether visitors are supported.
* @returns {{
* type: SET_VISITORS_SUPPORTED,
* }}
*/
export function setVisitorsSupported(value: boolean) {
return {
type: SET_VISITORS_SUPPORTED,
value
};
}
/**
* Visitors count has been updated.
*

View File

@@ -1,3 +0,0 @@
import { getLogger } from '../base/logging/functions';
export default getLogger('features/visitors');

View File

@@ -7,11 +7,8 @@ import {
CONFERENCE_JOIN_IN_PROGRESS,
ENDPOINT_MESSAGE_RECEIVED
} from '../base/conference/actionTypes';
import { connect, setPreferVisitor } from '../base/connection/actions';
import { disconnect } from '../base/connection/actions.any';
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
import { raiseHand } from '../base/participants/actions';
import { getLocalParticipant, getParticipantById } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { BUTTON_TYPES } from '../base/ui/constants.any';
import { hideNotification, showNotification } from '../notifications/actions';
@@ -20,7 +17,6 @@ import {
NOTIFICATION_TIMEOUT_TYPE,
VISITORS_PROMOTION_NOTIFICATION_ID
} from '../notifications/constants';
import { INotificationProps } from '../notifications/types';
import { open as openParticipantsPane } from '../participants-pane/actions';
import {
@@ -28,12 +24,9 @@ import {
clearPromotionRequest,
denyRequest,
promotionRequestReceived,
setVisitorDemoteActor,
setVisitorsSupported,
updateVisitorsCount
} from './actions';
import { getPromotionRequests } from './functions';
import logger from './logger';
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
switch (action.type) {
@@ -53,70 +46,28 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { conference } = action;
if (getState()['features/visitors'].iAmVisitor) {
const { demoteActorDisplayName } = getState()['features/visitors'];
dispatch(setVisitorDemoteActor(undefined));
const notificationParams: INotificationProps = {
dispatch(showNotification({
titleKey: 'visitors.notification.title',
descriptionKey: 'visitors.notification.description'
};
if (demoteActorDisplayName) {
notificationParams.descriptionKey = 'visitors.notification.demoteDescription';
notificationParams.descriptionArguments = {
actor: demoteActorDisplayName
};
}
// check for demote actor and update notification
dispatch(showNotification(notificationParams, NOTIFICATION_TIMEOUT_TYPE.STICKY));
} else {
dispatch(setVisitorsSupported(conference.isVisitorsSupported()));
conference.on(JitsiConferenceEvents.VISITORS_SUPPORTED_CHANGED, (value: boolean) => {
dispatch(setVisitorsSupported(value));
});
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
}
conference.on(JitsiConferenceEvents.VISITORS_MESSAGE, (
msg: { action: string; actor: string; from: string; id: string; nick: string; on: boolean; }) => {
msg: { from: string; nick: string; on: boolean; }) => {
const request = {
from: msg.from,
nick: msg.nick
};
if (msg.action === 'demote-request') {
// we need it before the disconnect
const participantById = getParticipantById(getState, msg.actor);
const localParticipant = getLocalParticipant(getState);
if (localParticipant && localParticipant.id === msg.id) {
// handle demote
dispatch(disconnect(true))
.then(() => dispatch(setPreferVisitor(true)))
.then(() => {
// we need to set the name, so we can use it later in the notification
if (participantById) {
dispatch(setVisitorDemoteActor(participantById.name));
}
return dispatch(connect());
});
}
} else if (msg.action === 'promotion-request') {
const request = {
from: msg.from,
nick: msg.nick
};
if (msg.on) {
dispatch(promotionRequestReceived(request));
} else {
dispatch(clearPromotionRequest(request));
}
_handlePromotionNotification({
dispatch,
getState
});
if (msg.on) {
dispatch(promotionRequestReceived(request));
} else {
logger.error('Unknown action:', msg.action);
dispatch(clearPromotionRequest(request));
}
_handlePromotionNotification({
dispatch,
getState
});
});
conference.on(JitsiConferenceEvents.VISITORS_REJECTION, () => {
@@ -186,7 +137,7 @@ function _handlePromotionNotification(
waitingParticipants: requests.length
});
icon = NOTIFICATION_ICON.PARTICIPANTS;
customActionNameKey = [ 'notify.viewVisitors' ];
customActionNameKey = [ 'notify.viewLobby' ];
customActionType = [ BUTTON_TYPES.PRIMARY ];
customActionHandler = [ () => batch(() => {
dispatch(hideNotification(VISITORS_PROMOTION_NOTIFICATION_ID));

View File

@@ -4,8 +4,6 @@ import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
CLEAR_VISITOR_PROMOTION_REQUEST,
I_AM_VISITOR_MODE,
SET_VISITORS_SUPPORTED,
SET_VISITOR_DEMOTE_ACTOR,
UPDATE_VISITORS_COUNT,
VISITOR_PROMOTION_REQUEST
} from './actionTypes';
@@ -15,16 +13,13 @@ const DEFAULT_STATE = {
count: -1,
iAmVisitor: false,
showNotification: false,
supported: false,
promotionRequests: []
};
export interface IVisitorsState {
count?: number;
demoteActorDisplayName?: string;
iAmVisitor: boolean;
promotionRequests: IPromotionRequest[];
supported: boolean;
}
ReducerRegistry.register<IVisitorsState>('features/visitors', (state = DEFAULT_STATE, action): IVisitorsState => {
switch (action.type) {
@@ -55,18 +50,6 @@ ReducerRegistry.register<IVisitorsState>('features/visitors', (state = DEFAULT_S
iAmVisitor: action.enabled
};
}
case SET_VISITOR_DEMOTE_ACTOR: {
return {
...state,
demoteActorDisplayName: action.displayName
};
}
case SET_VISITORS_SUPPORTED: {
return {
...state,
supported: action.value
};
}
case VISITOR_PROMOTION_REQUEST: {
const currentRequests = state.promotionRequests || [];

View File

@@ -238,6 +238,16 @@ function destroy_lobby_room(room, newjid, message)
end
end
-- handle multiple items at once
function handle_admin_query_set_command(self, origin, stanza)
for i=1,#stanza.tags[1] do
if handle_admin_query_set_command_item(self, origin, stanza, stanza.tags[1].tags[i]) then
return true;
end
end
return true;
end
-- This is a copy of the function(handle_admin_query_set_command) from prosody 12 (d7857ef7843a)
function handle_admin_query_set_command_item(self, origin, stanza, item)
if not item then
@@ -299,58 +309,6 @@ function handle_admin_query_set_command_item(self, origin, stanza, item)
end
end
-- this is extracted from prosody to handle multiple invites
function handle_mediated_invite(room, origin, stanza, payload, host_module)
local invitee = jid_prep(payload.attr.to);
if not invitee then
origin.send(st.error_reply(stanza, "cancel", "jid-malformed"));
return true;
elseif host_module:fire_event("muc-pre-invite", {room = room, origin = origin, stanza = stanza}) then
return true;
end
local invite = muc_util.filter_muc_x(st.clone(stanza));
invite.attr.from = room.jid;
invite.attr.to = invitee;
invite:tag('x', {xmlns='http://jabber.org/protocol/muc#user'})
:tag('invite', {from = stanza.attr.from;})
:tag('reason'):text(payload:get_child_text("reason")):up()
:up()
:up();
if not host_module:fire_event("muc-invite", {room = room, stanza = invite, origin = origin, incoming = stanza}) then
room:route_stanza(invite);
end
return true;
end
local prosody_overrides = {
-- handle multiple items at once
handle_admin_query_set_command = function(self, origin, stanza)
for i=1,#stanza.tags[1] do
if handle_admin_query_set_command_item(self, origin, stanza, stanza.tags[1].tags[i]) then
return true;
end
end
return true;
end,
-- this is extracted from prosody to handle multiple invites
handle_message_to_room = function(room, origin, stanza, host_module)
local type = stanza.attr.type;
if type == nil or type == "normal" then
local x = stanza:get_child("x", "http://jabber.org/protocol/muc#user");
if x then
local handled = false;
for _, payload in pairs(x.tags) do
if payload ~= nil and payload.name == "invite" and payload.attr.to then
handled = true;
handle_mediated_invite(room, origin, stanza, payload, host_module)
end
end
return handled;
end
end
end
};
-- operates on already loaded lobby muc module
function process_lobby_muc_loaded(lobby_muc, host_module)
module:log('debug', 'Lobby muc loaded');
@@ -562,10 +520,8 @@ process_host_module(main_muc_component_config, function(host_module, host)
for event_name, method in pairs {
-- Normal room interactions
["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ;
["message/bare"] = "handle_message_to_room" ;
-- Host room
["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ;
["message/host"] = "handle_message_to_room" ;
} do
host_module:hook(event_name, function (event)
local origin, stanza = event.origin, event.stanza;
@@ -573,7 +529,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
local room = get_room_from_jid(room_jid);
if room then
return prosody_overrides[method](room, origin, stanza, host_module);
return handle_admin_query_set_command(room, origin, stanza);
end
end, 1) -- make sure we handle it before prosody that uses priority -2 for this
end

View File

@@ -22,9 +22,7 @@ local function load_config()
-- Max allowed login rate in events per second.
config.login_rate = module:get_option_number("rate_limit_login_rate", 3);
-- The rate to which sessions from IPs exceeding the join rate will be limited, in bytes per second.
config.ip_rate = module:get_option_number("rate_limit_ip_rate", 2000);
-- The rate to which sessions exceeding the stanza(iq, presence, message) rate will be limited, in bytes per second.
config.session_rate = module:get_option_number("rate_limit_session_rate", 1000);
config.session_rate = module:get_option_number("rate_limit_session_rate", 2000);
-- The time in seconds, after which the limit for an IP address is lifted.
config.timeout = module:get_option_number("rate_limit_timeout", 60);
-- List of regular expressions for IP addresses that are not limited by this module.
@@ -35,7 +33,7 @@ local function load_config()
-- Max allowed presence rate in events per second.
config.presence_rate = module:get_option_number("rate_limit_presence_rate", 4);
-- Max allowed iq rate in events per second.
config.iq_rate = module:get_option_number("rate_limit_iq_rate", 15);
config.iq_rate = module:get_option_number("rate_limit_iq_rate", 10);
-- Max allowed message rate in events per second.
config.message_rate = module:get_option_number("rate_limit_message_rate", 3);
@@ -47,8 +45,8 @@ local function load_config()
local wl_hosts = "";
for j in config.whitelist_hosts do wl_hosts = wl_hosts .. j .. "," end
module:log("info", "Loaded configuration: ");
module:log("info", "- ip_rate=%s bytes/sec, session_rate=%s bytes/sec, timeout=%s sec, cache size=%s, whitelist=%s, whitelist_hosts=%s",
config.ip_rate, config.session_rate, config.timeout, config.cache_size, wl, wl_hosts);
module:log("info", "- session_rate=%s bytes/sec, timeout=%s sec, cache size=%s, whitelist=%s, whitelist_hosts=%s",
config.session_rate, config.timeout, config.cache_size, wl, wl_hosts);
module:log("info", "- login_rate=%s/sec, presence_rate=%s/sec, iq_rate=%s/sec, message_rate=%s/sec",
config.login_rate, config.presence_rate, config.iq_rate, config.message_rate);
end
@@ -96,7 +94,7 @@ local function limit_bytes_in(bytes, session)
if sess_throttle then
-- if the limit timeout has elapsed let's stop the throttle
if not sess_throttle.start or gettime() - sess_throttle.start > config.timeout then
module:log("info", "Stop throttling session=%s, ip=%s.", session.id, session.ip);
module:log("info", "Stop throttling session=%s, ip=%s.", session, session.ip);
session.jitsi_throttle = nil;
return bytes;
end
@@ -123,30 +121,15 @@ local function limit_bytes_in(bytes, session)
end
-- Throttles reading from the connection of a specific session.
local function throttle_session(session, rate, timeout)
if not session.jitsi_throttle then
local function throttle_session(session)
if not session.jitsi_throttle then
if (session.conn and session.conn.setlimit) then
session.jitsi_throttle_counter = session.jitsi_throttle_counter + 1;
module:log("info", "Enabling throttle (%s bytes/s) via setlimit, session=%s, ip=%s, counter=%s.",
rate, session.id, session.ip, session.jitsi_throttle_counter);
session.conn:setlimit(rate);
if timeout then
if session.jitsi_throttle_timer then
-- if there was a timer stop it as we will schedule a new one
session.jitsi_throttle_timer:stop();
session.jitsi_throttle_timer = nil;
end
session.jitsi_throttle_timer = module:add_timer(timeout, function()
if session.conn then
module:log("info", "Stop throttling session=%s, ip=%s.", session.id, session.ip);
session.conn:setlimit(0);
end
session.jitsi_throttle_timer = nil;
end);
end
-- TODO: we don't have a mechanism to unthrottle a session in this case.
module:log("info", "Enabling throttle (%s bytes/s) via setlimit, session=%s, ip=%s.", config.session_rate, session.id, session.ip);
session.conn:setlimit(config.session_rate);
else
module:log("info", "Enabling throttle (%s bytes/s) via filter, session=%s, ip=%s.", rate, session.id, session.ip);
session.jitsi_throttle = new_throttle(rate, 2);
module:log("info", "Enabling throttle (%s bytes/s) via filter, session=%s, ip=%s.", config.session_rate, session.id, session.ip);
session.jitsi_throttle = new_throttle(config.session_rate, 2);
filters.add_filter(session, "bytes/in", limit_bytes_in, 1000);
-- throttle.start used for stop throttling after the timeout
session.jitsi_throttle.start = gettime();
@@ -164,7 +147,7 @@ function filter_stanza(stanza, session)
local ok, _, _ = rate:poll(1, true);
if not ok then
module:log("info", "%s rate exceeded for %s, limiting.", stanza.name, session.full_jid);
throttle_session(session, config.session_rate, config.timeout);
throttle_session(session);
end
end
@@ -183,6 +166,7 @@ local function on_login(session, ip)
if not ok then
module:log("info", "Join rate exceeded for %s, limiting.", ip);
limit_ip(ip);
throttle_session(session);
end
end
@@ -202,7 +186,6 @@ local function filter_hook(session)
on_login(session, ip);
-- creates the stanzas rates
session.jitsi_throttle_counter = 0;
session.presence_rate = new_throttle(config.presence_rate, 2);
session.iq_rate = new_throttle(config.iq_rate, 2);
session.message_rate = new_throttle(config.message_rate, 2);
@@ -212,12 +195,12 @@ local function filter_hook(session)
if oldt then
local newt = gettime();
local elapsed = newt - oldt;
if elapsed < config.timeout then
if elapsed < 5 then
module:log("info", "IP address %s was limited %s seconds ago, refreshing.", ip, elapsed);
limited_ips:set(ip, newt);
end
throttle_session(session, config.ip_rate);
if elapsed < 5 then
module:log("info", "IP address %s was limitted %s seconds ago, refreshing.", ip, elapsed);
limited_ips:set(ip, newt);
throttle_session(session);
elseif elapsed < config.timeout then
throttle_session(session);
else
module:log("info", "Removing the limit for %s", ip);
limited_ips:set(ip, nil);

View File

@@ -15,7 +15,6 @@ local process_host_module = util.process_host_module;
local new_id = require 'util.id'.medium;
local um_is_admin = require 'core.usermanager'.is_admin;
local json = require 'util.json';
local inspect = require 'inspect';
local MUC_NS = 'http://jabber.org/protocol/muc';
@@ -31,10 +30,6 @@ local ignore_list = module:context(muc_domain_base):get_option_set('visitors_ign
local auto_allow_promotion = module:get_option_boolean('auto_allow_visitor_promotion', false);
-- whether to always advertise that visitors feature is enabled for rooms
-- can be set to off and being controlled by another module, turning it on and off for rooms
local always_visitors_enabled = module:get_option_boolean('always_visitors_enabled', true);
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
@@ -95,13 +90,11 @@ local function request_promotion_received(room, from_jid, from_vnode, nick, time
local iq_id = new_id();
sent_iq_cache:set(iq_id, socket.gettime());
local node = jid.node(room.jid);
module:send(st.iq({
type='set', to = req_from, from = module.host, id = iq_id })
:tag('visitors', {
xmlns='jitsi:visitors',
room = jid.join(node, muc_domain_prefix..'.'..req_from),
room = string.gsub(room.jid, muc_domain_base, req_from),
focusjid = focus_jid })
:tag('promotion-response', {
xmlns='jitsi:visitors',
@@ -268,37 +261,6 @@ local function stanza_handler(event)
return processed;
end
local function process_promotion_response(room, id, approved)
-- lets reply to participant that requested promotion
local username = new_id():lower();
visitors_promotion_map[room.jid][username] = {
from = visitors_promotion_requests[room.jid][id].from;
jid = id;
};
local req_from = visitors_promotion_map[room.jid][username].from;
local req_jid = visitors_promotion_map[room.jid][username].jid;
local focus_occupant = get_focus_occupant(room);
local focus_jid = focus_occupant and focus_occupant.bare_jid or nil;
local iq_id = new_id();
sent_iq_cache:set(iq_id, socket.gettime());
local node = jid.node(room.jid);
module:send(st.iq({
type='set', to = req_from, from = module.host, id = iq_id })
:tag('visitors', {
xmlns='jitsi:visitors',
room = jid.join(node, muc_domain_prefix..'.'..req_from),
focusjid = focus_jid })
:tag('promotion-response', {
xmlns='jitsi:visitors',
jid = req_jid,
username = username,
allow = approved }):up());
end
module:hook('iq/host', stanza_handler, 10);
process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_module, host)
@@ -387,8 +349,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return;
end
local data = json.decode(json_data);
if not data or data.type ~= 'visitors'
or (data.action ~= "promotion-response" and data.action ~= "demote-request") then
if not data or data.type ~= 'visitors' or data.action ~= "promotion-response" then
return;
end
@@ -406,58 +367,48 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return false;
end
if data.action == "demote-request" then
if occupant.nick ~= room.jid..'/'..data.actor then
module:log('error', 'Bad actor in demote request %s', stanza);
event.origin.send(st.error_reply(stanza, "cancel", "bad-request"));
return true;
end
-- when demoting we want to send message to the demoted participant and to moderators
local target_jid = room.jid..'/'..data.id;
stanza.attr.type = 'chat'; -- it is safe as we are not using this stanza instance anymore
stanza.attr.from = module.host;
for _, room_occupant in room:each_occupant() do
-- do not send it to jicofo or back to the sender
if room_occupant.jid ~= occupant.jid and not is_admin(room_occupant.bare_jid) then
if room_occupant.role == 'moderator'
or room_occupant.nick == target_jid then
stanza.attr.to = room_occupant.jid;
room:route_stanza(stanza);
end
end
end
else
if data.id then
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
else
-- we are in the case with admit all, we need to read data.ids
for i in pairs(data.ids) do
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
end
-- let's forward to every moderator, this is so they now that this moderator
-- took action and they can update UI, as this msg was initially a group chat but we are
-- sending it now as provide chat, let's change the type
stanza.attr.type = 'chat'; -- it is safe as we are not using this stanza instance anymore
for _, room_occupant in room:each_occupant() do
-- if moderator send the message
if room_occupant.role == 'moderator'
and room_occupant.jid ~= occupant.jid
and not is_admin(room_occupant.bare_jid) then
stanza.attr.to = room_occupant.nick;
room:route_stanza(stanza);
end
end
-- lets reply to participant that requested promotion
local username = new_id():lower();
visitors_promotion_map[room.jid][username] = {
from = visitors_promotion_requests[room.jid][data.id].from;
jid = data.id;
};
local req_from = visitors_promotion_map[room.jid][username].from;
local req_jid = visitors_promotion_map[room.jid][username].jid;
local focus_occupant = get_focus_occupant(room);
local focus_jid = focus_occupant and focus_occupant.bare_jid or nil;
local iq_id = new_id();
sent_iq_cache:set(iq_id, socket.gettime());
module:send(st.iq({
type='set', to = req_from, from = module.host, id = iq_id })
:tag('visitors', {
xmlns='jitsi:visitors',
room = string.gsub(room.jid, muc_domain_base, req_from),
focusjid = focus_jid })
:tag('promotion-response', {
xmlns='jitsi:visitors',
jid = req_jid,
username = username ,
allow = data.approved and 'true' or 'false' }):up());
return true; -- halt processing, but return true that we handled it
end);
if always_visitors_enabled then
local visitorsEnabledField = {
name = "muc#roominfo_visitorsEnabled";
type = "boolean";
label = "Whether visitors are enabled.";
value = 1;
};
-- Append "visitors enabled" to the MUC config form.
host_module:context(host):hook("muc-disco#info", function(event)
table.insert(event.form, visitorsEnabledField);
end);
host_module:context(host):hook("muc-config-form", function(event)
table.insert(event.form, visitorsEnabledField);
end);
end
end);
prosody.events.add_handler('pre-jitsi-authentication', function(session)

View File

@@ -439,8 +439,7 @@ function Util:verify_room(session, room_address)
if session.jitsi_meet_str_tenant
and string.lower(session.jitsi_meet_str_tenant) ~= session.jitsi_web_query_prefix then
module:log('warn', 'Tenant differs for user:%s group:%s url_tenant:%s token_tenant:%s',
session.jitsi_meet_context_user and session.jitsi_meet_context_user.id or '',
session.jitsi_meet_context_group,
inspect(session.jitsi_meet_context_user), session.jitsi_meet_context_group,
session.jitsi_web_query_prefix, session.jitsi_meet_str_tenant);
session.jitsi_meet_tenant_mismatch = true;
end