Compare commits

...

16 Commits

Author SHA1 Message Date
Calinteodor
8a4990d9ae sdk(react-native-sdk): rnsdk screenshare android fix (#13884)
sdk(react-native-sdk): rnsdk screenshare android fix
2023-11-07 12:22:02 +02:00
Дамян Минков
0e55cbbda6 Clean up prosody modules with some extra checks (#14020)
* fix: Adds check for jitsi_meet_room not being string.

Oct 20 12:22:50 mod_bosh        error   Traceback[bosh]: /usr/share/jitsi-meet/prosody-plugins/token/util.lib.lua:336: bad argument #1 to 'lower' (string expected, got userdata)
        stack traceback:
        [C]: in function 'lower'
        /usr/share/jitsi-meet/prosody-plugins/token/util.lib.lua:336: in function 'verify_room'
        ...re/jitsi-meet/prosody-plugins/mod_token_verification.lua:78: in function 'verify_user'

* fix: Adds check for missing speaker stats for occupant.

error   Traceback[c2s]: ...itsi-meet/prosody-plugins/mod_speakerstats_component.lua:124: attempt to index field '?' (a nil value)
        stack traceback:
        ...itsi-meet/prosody-plugins/mod_speakerstats_component.lua:124: in function '?'

* fix: Nil check for breakout_rooms.

c2saaaad95a16c0 error   Traceback[c2s]: ...re/jitsi-meet/prosody-plugins/mod_muc_breakout_rooms.lua:345: attempt to index local 'main_room' (a nil value)
        stack traceback:
        ...re/jitsi-meet/prosody-plugins/mod_muc_breakout_rooms.lua:345: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
        (...tail calls...)
        /usr/lib/prosody/modules/muc/muc.lib.lua:496: in function </usr/lib/prosody/modules/muc/muc.lib.lua:492>

* fix: Adds nil check in allowners.

c2saaaae3024810 error   Traceback[c2s]: /usr/share/jitsi-meet/prosody-plugins/mod_muc_allowners.lua:171: attempt to index local 'room' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_muc_allowners.lua:171: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>

* fix: Adds nil check in lobby.

mod_bosh        error   Traceback[bosh]: ...share/jitsi-meet/prosody-plugins/mod_muc_lobby_rooms.lua:168: attempt to index local 'lobby_room' (a nil value)
        stack traceback:
        ...share/jitsi-meet/prosody-plugins/mod_muc_lobby_rooms.lua:168: in function '?'
        /usr/share/lua/5.2/prosody/util/filters.lua:25: in function 'filter'
        /usr/lib/prosody/modules/mod_bosh.lua:361: in function 'send'
        /usr/lib/prosody/modules/muc/mod_muc.lua:495: in function '?'

* fix: Fixes nil error in fmuc.

s2sinaaaaf2817260       error   Traceback[s2s]: /usr/share/jitsi-meet/prosody-plugins/mod_fmuc.lua:295: attempt to index local 'occupant' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_fmuc.lua:295: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
        (...tail calls...)
        /usr/lib/prosody/modules/muc/muc.lib.lua:1201: in function </usr/lib/prosody/modules/muc/muc.lib.lua:1194>

* fix: Fixes nil occupant.

c2s55f4d5411dd0 error   Traceback[c2s]: /usr/share/jitsi-meet/prosody-plugins/mod_muc_flip.lua:120: attempt to index local 'kicked_occupant' (a nil value)
        stack traceback:
        /usr/share/jitsi-meet/prosody-plugins/mod_muc_flip.lua:120: in function '?'
        /usr/share/lua/5.2/prosody/util/events.lua:81: in function </usr/share/lua/5.2/prosody/util/events.lua:77>
        (...tail calls...)
        /usr/lib/prosody/modules/muc/muc.lib.lua:791: in function </usr/lib/prosody/modules/muc/muc.lib.lua:616>

* fix: Fixes caching main room.

Objects should not be set in room._data as this field is being serialized and we see errors like.

error   Traceback[c2s]: /usr/share/lua/5.2/prosody/util/serialization.lua:34: Can't serialize userdata
        stack traceback:
        [C]: in function 'error'
        /usr/share/lua/5.2/prosody/util/serialization.lua:34: in function </usr/share/lua/5.2/prosody/util/serialization.lua:33>
        (...tail calls...)
        /usr/share/lua/5.2/prosody/util/serialization.lua:199: in function 'serialize_table'
        /usr/share/lua/5.2/prosody/util/serialization.lua:197: in function 'serialize_table'
        /usr/share/lua/5.2/prosody/util/serialization.lua:197: in function 'serialize_table'
        /usr/share/lua/5.2/prosody/util/serialization.lua:219: in function </usr/share/lua/5.2/prosody/util/serialization.lua:217>
        (...tail calls...)
        /usr/lib/prosody/modules/mod_storage_memory.lua:42: in function </usr/lib/prosody/modules/mod_storage_memory.lua:40>
        (...tail calls...)
        ...re/jitsi-meet/prosody-plugins/mod_muc_breakout_rooms.lua:207: in function 'create_breakout_room'

* fix: Fixes calling save_occupant after changing its role.

* squash: Fixed passed value to type.
2023-11-06 15:31:59 -06:00
damencho
6da94aecf2 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1718.0.0+30be4f6f...v1719.0.0+f8a18cf0
2023-11-06 09:40:48 -06:00
Calinteodor
2a3c962e88 feat(recent-list): fix undefined error that breaks visitor joining (#14024)
* feat(recent-list): fix undefined error that breaks visitor joining

* feat(recent-list): revert variable name change

* feat(recent-list): fixed linter
2023-11-06 09:40:28 -06:00
AHMAD KADRI
34f1eb60f4 Accessibility: add validation warning on room name (#14009)
feat(accessibility): add validation warning on room name
2023-11-06 10:59:51 +02:00
Jaya Allamsetty
4115ebe856 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1717.0.0+7b6ce949...v1718.0.0+30be4f6f
2023-11-02 15:36:11 -04:00
Horatiu Muresan
d7dadfc157 feat(facing-mode) add config for initial camera facing mode (#14013) 2023-11-02 16:20:38 +02:00
Erin Yuki Schlarb
2851eeeab3 fix: Make room_metadata Prosody module depend on the required jitsi_session module
Without this room_metadata will silently discard all room metadata client requests assuming that they didn’t come from Jitsi meet clients.

Fixes #14001
2023-11-01 17:06:26 -05:00
Muhammed Ajmal M
84d75f2ae8 fix(screen-sharing) Self view of SC sized correctly initially (#13992) 2023-11-01 18:32:34 +02:00
damencho
73b3309adf feat: Adds leave rate limit to muc_rate_limit. 2023-10-31 15:59:23 -05:00
Jaya Allamsetty
e2de06f60d chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1716.0.0+93c167d3...v1717.0.0+7b6ce949
2023-10-31 14:35:31 -04:00
damencho
cdc7962d11 feat: Adds region parameter to dial out authorize requests. 2023-10-31 11:45:06 -05:00
Saúl Ibarra Corretgé
59242e1217 feat(external-api) introduce a "ready" event
It's fired when the API is ready, and it signals the embedder that they
can reveal the meeting from behind an overlay, for example.

The astute reader might notice we are currently sending a
'browser-support' event roughly at the same time. The reason for this
new event is plain simply semantics.

In addition the 'onload' handler is faked by calling it when the new
ready event fires. The original onload event is unreliable. It will be
called even when nothing was ever loaded (try loading a page without
internet and be amused).
2023-10-31 16:27:12 +01:00
Saúl Ibarra Corretgé
631e39d4fd feat(external-api) allow vh and vw values as parameters 2023-10-31 16:27:12 +01:00
Julian LADJANI
4290cdf53d fix(breakout-rooms, feature-flags): handle breakout button feature flag on participant pane footer component (#14003)
* fix(breakout-rooms, feature-flags): handle breakout button feature flag on participant pane footer component
2023-10-31 13:53:41 +02:00
damencho
84c1e20216 fix(moderated): Fixes moderators in moderated rooms without tenant. 2023-10-30 17:26:42 -05:00
39 changed files with 619 additions and 93 deletions

4
.gitignore vendored
View File

@@ -99,6 +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

@@ -218,6 +218,9 @@ var config = {
// Video
// Sets the default camera facing mode.
// cameraFacingMode: 'user',
// Sets the preferred resolution (height) for local video. Defaults to 720.
// resolution: 720,

View File

@@ -61,6 +61,35 @@ body.welcome-page {
}
.not-allow-title-character-div {
color: #f03e3e;
background-color: #fff;
font-size: 12px;
font-weight: 600;
margin: 10px 0px 5px 0px;
text-align: $welcomePageHeaderTextAlign;
border-radius: 5px;
padding: 5px;
.not-allow-title-character-text {
float: right;
line-height: 1.9;
};
.jitsi-icon {
margin-right: 9px;
float: left;
svg {
fill:#f03e3e;
& > *:first-child {
fill: none !important;
}
}
}
}
.insecure-room-name-warning {
align-items: center;
color: rgb(215, 121, 118);

View File

@@ -1107,7 +1107,11 @@ class API {
this._enabled = true;
initCommands();
this.notifyBrowserSupport(isSupportedBrowser());
// Let the embedder know we are ready.
this._sendEvent({ name: 'ready' });
}
/**

View File

@@ -145,6 +145,7 @@ const events = {
'prejoin-screen-loaded': 'prejoinScreenLoaded',
'proxy-connection-event': 'proxyConnectionEvent',
'raise-hand-updated': 'raiseHandUpdated',
'ready': 'ready',
'recording-link-available': 'recordingLinkAvailable',
'recording-status-changed': 'recordingStatusChanged',
'participant-menu-button-clicked': 'participantMenuButtonClick',
@@ -273,10 +274,10 @@ function parseArguments(args) {
function parseSizeParam(value) {
let parsedValue;
// This regex parses values of the form 100px, 100em, 100pt or 100%.
// This regex parses values of the form 100px, 100em, 100pt, 100vh, 100vw or 100%.
// Values like 100 or 100px are handled outside of the regex, and
// invalid values will be ignored and the minimum will be used.
const re = /([0-9]*\.?[0-9]+)(em|pt|px|%)$/;
const re = /([0-9]*\.?[0-9]+)(em|pt|px|((d|l|s)?v)(h|w)|%)$/;
if (typeof value === 'string' && String(value).match(re) !== null) {
parsedValue = value;
@@ -365,7 +366,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
},
release
});
this._createIFrame(height, width, onload, sandbox);
this._createIFrame(height, width, sandbox);
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
@@ -375,9 +378,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
}
})
});
if (Array.isArray(invitees) && invitees.length > 0) {
this.invite(invitees);
}
this._onload = onload;
this._tmpE2EEKey = e2eeKey;
this._isLargeVideoVisible = false;
this._isPrejoinVideoVisible = false;
@@ -396,14 +402,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* parseSizeParam for format details.
* @param {number|string} width - The with of the iframe. Check
* parseSizeParam for format details.
* @param {Function} onload - The function that will listen
* for onload event.
* @param {string} sandbox - Sandbox directive for the created iframe, if desired.
* @returns {void}
*
* @private
*/
_createIFrame(height, width, onload, sandbox) {
_createIFrame(height, width, sandbox) {
const frameName = `jitsiConferenceFrame${id}`;
this._frame = document.createElement('iframe');
@@ -427,11 +431,6 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._frame.sandbox = sandbox;
}
if (onload) {
// waits for iframe resources to load
// and fires event when it is done
this._frame.onload = onload;
}
this._frame.src = this._url;
this._frame = this._parentNode.appendChild(this._frame);
@@ -580,6 +579,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
const userID = data.id;
switch (name) {
case 'ready': {
// Fake the iframe onload event because it's not reliable.
this._onload?.();
break;
}
case 'video-conference-joined': {
if (typeof this._tmpE2EEKey !== 'undefined') {

10
package-lock.json generated
View File

@@ -59,7 +59,7 @@
"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/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1719.0.0+f8a18cf0/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -12756,8 +12756,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"integrity": "sha512-+6nI2ExiTS8aKnq3ZDpYKs1MSDtUHaK4hIhVxyI7Db6ZojrMOpZx6lnrmqz+AHWllSHvIggFz48du7tG6UYANw==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1719.0.0+f8a18cf0/lib-jitsi-meet.tgz",
"integrity": "sha512-OCu1RUkV7TjOApNgIgbGToLGgo4VNXWHuWuhouUVmzxjpQi8bDqGAvBENYLPnMcDBpPt8N1i5hN7Tz3TPxhmgQ==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -29782,8 +29782,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"integrity": "sha512-+6nI2ExiTS8aKnq3ZDpYKs1MSDtUHaK4hIhVxyI7Db6ZojrMOpZx6lnrmqz+AHWllSHvIggFz48du7tG6UYANw==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1719.0.0+f8a18cf0/lib-jitsi-meet.tgz",
"integrity": "sha512-OCu1RUkV7TjOApNgIgbGToLGgo4VNXWHuWuhouUVmzxjpQi8bDqGAvBENYLPnMcDBpPt8N1i5hN7Tz3TPxhmgQ==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -65,7 +65,7 @@
"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/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1719.0.0+f8a18cf0/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -73,6 +73,13 @@ cd ios && pod install && cd ..
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
```
- In `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
```xml
<service
android:name="org.jitsi.meet.sdk.JitsiMeetOngoingConferenceService"
android:foregroundServiceType="mediaProjection" />
```
This will take care of the screen share feature.
If you want to test all the steps before applying them to your app, you can check our React Native SDK sample app here:
https://github.com/jitsi/jitsi-meet-sdk-samples/tree/master/react-native

View File

@@ -0,0 +1,44 @@
package org.jitsi.meet.sdk;
import android.app.Notification;
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();
Context activityContext = context.getCurrentActivity();
JitsiMeetOngoingConferenceService.launch(context, activityContext);
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JitsiMeetOngoingConferenceService.abort(context);
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,100 @@
/*
* 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.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, Context activityContext) {
RNOngoingNotification.createOngoingConferenceNotificationChannel(activityContext);
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,97 @@
/*
* 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.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(Context activityContext) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (activityContext == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) activityContext.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", activityContext.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();
}
}

View File

@@ -6,7 +6,9 @@ const packageJSON = require('../package.json');
const SDKPackageJSON = require('./package.json');
const androidSourcePath = '../android/sdk/src/main/java/org/jitsi/meet/sdk';
const androidMainSourcePath = '../android/sdk/src/main/res';
const androidTargetPath = './android/src/main/java/org/jitsi/meet/sdk';
const androidMainTargetPath = './android/src/main/res';
const iosSrcPath = '../ios/sdk/src';
const iosDestPath = './ios/src';
@@ -169,6 +171,30 @@ copyFolderRecursiveSync(
`${androidSourcePath}/log`,
`${androidTargetPath}/log`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/values`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-hdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-mdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidMainSourcePath}/drawable-xxxhdpi`,
`${androidMainTargetPath}`
);
copyFolderRecursiveSync(
`${androidSourcePath}/net`,
`${androidTargetPath}/log`

View File

@@ -253,6 +253,7 @@ export interface IConfig {
callStatsID?: string;
callStatsSecret?: string;
callUUID?: string;
cameraFacingMode?: string;
channelLastN?: number;
chromeExtensionBanner?: {
chromeExtensionsInfo?: Array<{ id: string; path: string; }>;

View File

@@ -74,6 +74,7 @@ export default [
*/
'callUUID',
'cameraFacingMode',
'conferenceInfo',
'channelLastN',
'connectionIndicators',

View File

@@ -23,7 +23,12 @@ import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { getPropertyValue } from '../settings/functions.any';
import { TRACK_ADDED } from '../tracks/actionTypes';
import { destroyLocalTracks } from '../tracks/actions.any';
import { isLocalTrackMuted, isLocalVideoTrackDesktop, setTrackMuted } from '../tracks/functions.any';
import {
getCameraFacingMode,
isLocalTrackMuted,
isLocalVideoTrackDesktop,
setTrackMuted
} from '../tracks/functions.any';
import { ITrack } from '../tracks/types';
import {
@@ -40,7 +45,6 @@ import {
setVideoMuted
} from './actions';
import {
CAMERA_FACING_MODE,
MEDIA_TYPE,
SCREENSHARE_MUTISM_AUTHORITY,
VIDEO_MUTISM_AUTHORITY
@@ -233,7 +237,7 @@ function _setRoom({ dispatch, getState }: IStore, next: Function, action: AnyAct
// the user i.e. the state of base/media. Eventually, practice/reality i.e.
// the state of base/tracks will or will not agree with the desires.
dispatch(setAudioMuted(audioMuted));
dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
dispatch(setCameraFacingMode(getCameraFacingMode(state)));
dispatch(setVideoMuted(videoMuted));
}

View File

@@ -35,6 +35,7 @@ import {
} from './actionTypes';
import {
createLocalTracksF,
getCameraFacingMode,
getLocalTrack,
getLocalTracks,
getLocalVideoTrack,
@@ -140,6 +141,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
getState
};
const promises = [];
const state = getState();
// The following executes on React Native only at the time of this
// writing. The effort to port Web's createInitialLocalTracks
@@ -153,7 +155,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
// device separately.
for (const device of devices) {
if (getLocalTrack(
getState()['features/base/tracks'],
state['features/base/tracks'],
device as MediaType,
/* includePending */ true)) {
throw new Error(`Local track for ${device} already exists`);
@@ -165,7 +167,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
cameraDeviceId: options.cameraDeviceId,
devices: [ device ],
facingMode:
options.facingMode || CAMERA_FACING_MODE.USER,
options.facingMode || getCameraFacingMode(state),
micDeviceId: options.micDeviceId
},
store)

View File

@@ -4,7 +4,7 @@ import {
} from '../config/functions.any';
import { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
import { gumPending } from '../media/actions';
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
import { CAMERA_FACING_MODE, MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
import { IMediaState } from '../media/reducer';
import { IGUMPendingState } from '../media/types';
import {
@@ -447,3 +447,13 @@ export function logTracksForParticipant(tracksState: ITrack[], participantId: st
logger.debug(`${logStringPrefix}${reason ? `(reason: ${reason})` : ''}:${tracksLogMsg}`);
}
/**
* Gets the default camera facing mode.
*
* @param {Object} state - The redux state.
* @returns {string} - The camera facing mode.
*/
export function getCameraFacingMode(state: IReduxState) {
return state['features/base/config'].cameraFacingMode ?? CAMERA_FACING_MODE.USER;
}

View File

@@ -1,6 +1,7 @@
import { IStore } from '../../app/types';
import JitsiMeetJS from '../lib-jitsi-meet';
import { getCameraFacingMode } from './functions.any';
import { ITrackOptions } from './types';
export * from './functions.any';
@@ -39,6 +40,7 @@ export function createLocalTracksF(options: ITrackOptions = {}, store: IStore) {
// Copy array to avoid mutations inside library.
devices: options.devices?.slice(0),
facingMode: options.facingMode || getCameraFacingMode(state),
micDeviceId,
resolution
});

View File

@@ -12,6 +12,7 @@ import {
getUserSelectedMicDeviceId
} from '../settings/functions.web';
import { getCameraFacingMode } from './functions.any';
import loadEffects from './loadEffects';
import logger from './logger';
import { ITrackOptions } from './types';
@@ -82,6 +83,7 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore)
// Copy array to avoid mutations inside library.
devices: options.devices?.slice(0),
effects,
facingMode: options.facingMode || getCameraFacingMode(state),
firefox_fake_device, // eslint-disable-line camelcase
firePermissionPromptIsShownEvent,
micDeviceId,

View File

@@ -3,6 +3,7 @@ import { Component } from 'react';
import { createInviteDialogEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { getMeetingRegion } from '../../../base/config/functions.any';
import { showErrorNotification, showNotification } from '../../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
import { INotificationProps } from '../../../notifications/types';
@@ -65,6 +66,11 @@ export interface IProps {
*/
_peopleSearchUrl: string;
/**
* The region where we connected to.
*/
_region: string;
/**
* Whether or not to allow sip invites.
*/
@@ -248,6 +254,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
_jwt: jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl,
_region: region,
_sipInviteEnabled: sipInviteEnabled
} = this.props;
const options = {
@@ -259,6 +266,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
jwt,
peopleSearchQueryTypes,
peopleSearchUrl,
region,
sipInviteEnabled
};
@@ -300,6 +308,7 @@ export function _mapStateToProps(state: IReduxState) {
_jwt: state['features/base/jwt'].jwt ?? '',
_peopleSearchQueryTypes: peopleSearchQueryTypes ?? [],
_peopleSearchUrl: peopleSearchUrl ?? '',
_region: getMeetingRegion(state),
_sipInviteEnabled: isSipInviteEnabled(state)
};
}

View File

@@ -41,13 +41,15 @@ export const sharingFeatures = {
*
* @param {string} dialNumber - The dial number to check for validity.
* @param {string} dialOutAuthUrl - The endpoint to use for checking validity.
* @param {string} region - The region we are connected to.
* @returns {Promise} - The promise created by the request.
*/
export function checkDialNumber(
dialNumber: string,
dialOutAuthUrl: string
dialOutAuthUrl: string,
region: string
): Promise<{ allow?: boolean; country?: string; phone?: string; }> {
const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`;
const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}&region=${region}`;
return new Promise((resolve, reject) =>
fetch(fullUrl)
@@ -146,6 +148,11 @@ export type GetInviteResultsOptions = {
*/
peopleSearchUrl: string;
/**
* The region we are connected to.
*/
region: string;
/**
* Whether or not to check sip invites.
*/
@@ -174,6 +181,7 @@ export function getInviteResultsForQuery(
dialOutEnabled,
peopleSearchQueryTypes,
peopleSearchUrl,
region,
sipInviteEnabled,
jwt
} = options;
@@ -217,7 +225,7 @@ export function getInviteResultsForQuery(
// so ensure only digits get sent.
numberToVerify = getDigitsOnly(numberToVerify);
phoneNumberPromise = checkDialNumber(numberToVerify, dialOutAuthUrl);
phoneNumberPromise = checkDialNumber(numberToVerify, dialOutAuthUrl, region);
} else if (dialOutEnabled && !dialOutAuthUrl) {
// fake having a country code to hide the country code reminder
hasCountryCode = true;

View File

@@ -228,16 +228,17 @@ class LargeVideo extends Component<IProps> {
* another container for the background and the
* largeVideoWrapper in order to hide/show them.
*/}
{ _displayScreenSharingPlaceholder ? <ScreenSharePlaceholder /> : <></>}
<div
id = 'largeVideoWrapper'
onTouchEnd = { this._onDoubleTap }
ref = { this._wrapperRef }
role = 'figure' >
{ _displayScreenSharingPlaceholder ? <ScreenSharePlaceholder /> : <video
<video
autoPlay = { !_noAutoPlayVideo }
id = 'largeVideo'
muted = { true }
playsInline = { true } /* for Safari on iOS to work */ /> }
playsInline = { true } /* for Safari on iOS to work */ />
</div>
</div>
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES

View File

@@ -15,7 +15,10 @@ const useStyles = makeStyles()(theme => {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute'
position: 'absolute',
top: 0,
left: 0,
zIndex: 2
},
content: {
display: 'flex',

View File

@@ -2,7 +2,7 @@ import { NativeModules } from 'react-native';
/**
* Determimes if the ExternalAPI native module is available.
* Determines if the ExternalAPI native module is available.
*
* @returns {boolean} If yes {@code true} otherwise {@code false}.
*/

View File

@@ -1,3 +1,5 @@
import { NativeModules } from 'react-native';
import { getAppProp } from '../../base/app/functions';
import {
CONFERENCE_BLURRED,
@@ -8,6 +10,7 @@ import {
} from '../../base/conference/actionTypes';
import { PARTICIPANT_JOINED } 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';
@@ -15,9 +18,12 @@ import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
import { isExternalAPIAvailable } from './functions';
const externalAPIEnabled = isExternalAPIAvailable();
const { JMOngoingConference } = NativeModules;
/**
* Check if native modules are being used or not. If not then the init of middleware doesn't happen.
* Check if native modules are being used or not.
* If not, then the init of middleware doesn't happen.
*/
!externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
const result = next(action);
@@ -56,5 +62,21 @@ const externalAPIEnabled = isExternalAPIAvailable();
}
return result;
}
});
/**
* Check if native modules are being used or not.
*/
!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

@@ -128,6 +128,7 @@ export const RoomParticipantContextMenu = ({
size = { AVATAR_SIZE } />,
text: entity?.participantName
} ] } />}
<ContextMenuItemGroup>
<div className = { styles.text }>
{t('breakoutRooms.actions.sendToBreakoutRoom')}

View File

@@ -6,6 +6,10 @@ import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { openDialog, openSheet } from '../../../base/dialog/actions';
import {
BREAKOUT_ROOMS_BUTTON_ENABLED
} from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconDotsHorizontal, IconRingGroup } from '../../../base/icons/svg';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
@@ -34,6 +38,9 @@ const ParticipantsPaneFooter = (): JSX.Element => {
const isBreakoutRoomsSupported = useSelector((state: IReduxState) =>
state['features/base/conference'].conference?.getBreakoutRooms()?.isSupported()
);
const isBreakoutRoomsEnabled = useSelector((state: IReduxState) =>
getFeatureFlag(state, BREAKOUT_ROOMS_BUTTON_ENABLED, true)
);
const openMoreMenu = useCallback(() => dispatch(openSheet(ContextMenuMore)), [ dispatch ]);
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
[ dispatch ]);
@@ -44,6 +51,7 @@ const ParticipantsPaneFooter = (): JSX.Element => {
<View style = { styles.participantsPaneFooterContainer as ViewStyle }>
{
isBreakoutRoomsSupported
&& isBreakoutRoomsEnabled
&& <Button
accessibilityLabel = 'participantsPane.actions.breakoutRooms'
// eslint-disable-next-line react/jsx-no-bind, no-confusing-arrow

View File

@@ -83,7 +83,8 @@ function _appWillMount({ dispatch, getState }: IStore, next: Function, action: A
* @returns {*} The result returned by {@code next(action)}.
*/
function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
const { doNotStoreRoom } = getState()['features/base/config'];
const state = getState();
const { doNotStoreRoom } = state['features/base/config'];
if (!doNotStoreRoom && !inIframe()) {
let locationURL;
@@ -100,13 +101,17 @@ function _conferenceWillLeave({ dispatch, getState }: IStore, next: Function, ac
* JITSI_CONFERENCE_URL_KEY so we cannot call it and must use the other way.
*/
if (typeof APP === 'undefined') {
locationURL = action.conference[JITSI_CONFERENCE_URL_KEY];
const { conference } = action;
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
locationURL = conference && conference[JITSI_CONFERENCE_URL_KEY];
} else {
locationURL = getState()['features/base/connection'].locationURL;
locationURL = state['features/base/connection'].locationURL;
}
dispatch(
_updateConferenceDuration(
locationURL));
locationURL
));
}
return next(action);

View File

@@ -35,6 +35,7 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
_additionalCardTemplate: HTMLTemplateElement | null;
_additionalContentTemplate: HTMLTemplateElement | null;
_additionalToolbarContentTemplate: HTMLTemplateElement | null;
_titleHasNotAllowCharacter: boolean;
/**
* Default values for {@code WelcomePage} component's properties.
@@ -61,6 +62,14 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE
};
/**
* Used To display a warning massage if the title input has no allow character.
*
* @private
* @type {boolean}
*/
this._titleHasNotAllowCharacter = false;
/**
* The HTML Element used as the container for additional content. Used
* for directly appending the additional content template to the dom.
@@ -205,7 +214,7 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
<SettingsButton
defaultTab = { SETTINGS_TABS.CALENDAR }
isDisplayedOnWelcomePage = { true } />
{ showAdditionalToolbarContent
{showAdditionalToolbarContent
? <div
className = 'settings-toolbar-content'
ref = { this._setAdditionalToolbarContentRef } />
@@ -213,10 +222,10 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
}
</div>
<h1 className = 'header-text-title'>
{ t('welcomepage.headerTitle') }
{t('welcomepage.headerTitle')}
</h1>
<span className = 'header-text-subtitle'>
{ t('welcomepage.headerSubtitle')}
{t('welcomepage.headerSubtitle')}
</span>
<div id = 'enter_room'>
<div className = 'join-meeting-container'>
@@ -232,11 +241,11 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
pattern = { ROOM_NAME_VALIDATE_PATTERN_STR }
placeholder = { this.state.roomPlaceholder }
ref = { this._setRoomInputRef }
title = { t('welcomepage.roomNameAllowedChars') }
type = 'text'
value = { this.state.room } />
</form>
</div>
<button
aria-disabled = 'false'
aria-label = 'Start meeting'
@@ -245,17 +254,27 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
onClick = { this._onFormSubmit }
tabIndex = { 0 }
type = 'button'>
{ t('welcomepage.startMeeting') }
{t('welcomepage.startMeeting')}
</button>
</div>
</div>
{ this._renderInsecureRoomNameWarning() }
{this._titleHasNotAllowCharacter && (
<div
className = 'not-allow-title-character-div'
role = 'alert'>
<Icon src = { IconWarning } />
<span className = 'not-allow-title-character-text'>
{t('welcomepage.roomNameAllowedChars')}
</span>
</div>
)}
{this._renderInsecureRoomNameWarning()}
{ _moderatedRoomServiceUrl && (
{_moderatedRoomServiceUrl && (
<div id = 'moderated-meetings'>
{
translateToHTML(
t, 'welcomepage.moderatedMessage', { url: _moderatedRoomServiceUrl })
t, 'welcomepage.moderatedMessage', { url: _moderatedRoomServiceUrl })
}
</div>)}
</div>
@@ -264,22 +283,22 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
<div className = 'welcome-cards-container'>
<div className = 'welcome-card-column'>
<div className = 'welcome-tabs welcome-card welcome-card--blue'>
{ this._renderTabs() }
{this._renderTabs()}
</div>
{ showAdditionalCard
{showAdditionalCard
? <div
className = 'welcome-card welcome-card--dark'
ref = { this._setAdditionalCardRef } />
: null }
: null}
</div>
{ showAdditionalContent
{showAdditionalContent
? <div
className = 'welcome-page-content'
ref = { this._setAdditionalContentRef } />
: null }
: null}
</div>
{ DISPLAY_WELCOME_FOOTER && this._renderFooter()}
{DISPLAY_WELCOME_FOOTER && this._renderFooter()}
</div>
);
@@ -295,7 +314,7 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
<div className = 'insecure-room-name-warning'>
<Icon src = { IconWarning } />
<span>
{ getUnsafeRoomText(this.props.t, 'welcome') }
{getUnsafeRoomText(this.props.t, 'welcome')}
</span>
</div>
);
@@ -329,6 +348,9 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
// @ts-ignore
// eslint-disable-next-line require-jsdoc
_onRoomChange(event: React.ChangeEvent<HTMLInputElement>) {
const specialCharacters = [ '?', '&', ':', '\'', '"', '%', '#', '.' ];
this._titleHasNotAllowCharacter = specialCharacters.some(char => event.target.value.includes(char));
super._onRoomChange(event.target.value);
}
@@ -342,8 +364,10 @@ class WelcomePage extends AbstractWelcomePage<IProps> {
t,
_deeplinkingCfg: {
ios = { downloadLink: undefined },
android = { fDroidUrl: undefined,
downloadLink: undefined }
android = {
fDroidUrl: undefined,
downloadLink: undefined
}
}
} = this.props;

View File

@@ -292,10 +292,12 @@ end);
module:hook('muc-occupant-groupchat', function(event)
local occupant, room, stanza = event.occupant, event.room, event.stanza;
local from = stanza.attr.from;
local occupant_host = jid.host(occupant.bare_jid);
local occupant_host;
-- if there is no occupant this is a message from main, probably coming from other vnode
if occupant then
occupant_host = jid.host(occupant.bare_jid);
-- we manage nick only for visitors
if occupant_host ~= main_domain then
-- add to message stanza display name for the visitor

View File

@@ -50,9 +50,7 @@ module:hook("muc-occupant-pre-join", function (event)
return;
end
-- FIX ME: luacheck warning 581
-- not (x == y)' can be replaced by 'x ~= y' (if neither side is a table or NaN)
if not (subdomain == session.jitsi_meet_domain) then
if session.jitsi_meet_domain ~= '*' and subdomain ~= session.jitsi_meet_domain then
module:log('debug', 'skip allowners for auth user and non matching room subdomain: %s, jwt subdomain: %s',
subdomain, session.jitsi_meet_domain);
return;
@@ -138,7 +136,7 @@ function filter_admin_set_query(event)
local _aff = item.attr.affiliation;
-- if it is a moderated room we skip it
if is_moderated(room.jid) then
if room and is_moderated(room.jid) then
return nil;
end

View File

@@ -342,12 +342,11 @@ end
function on_breakout_room_pre_create(event)
local breakout_room = event.room;
local main_room, main_room_jid = get_main_room(breakout_room.jid);
local name = main_room._data.breakout_rooms[breakout_room.jid];
-- Only allow existent breakout rooms to be started.
-- Authorisation of breakout rooms is done by their random uuid name
if main_room and main_room._data.breakout_rooms and name then
breakout_room:set_subject(breakout_room.jid, name);
if main_room and main_room._data.breakout_rooms and main_room._data.breakout_rooms[breakout_room.jid] then
breakout_room:set_subject(breakout_room.jid, main_room._data.breakout_rooms[breakout_room.jid]);
else
module:log('debug', 'Invalid breakout room %s will not be created.', breakout_room.jid);
breakout_room:destroy(main_room_jid, 'Breakout room is invalid.');

View File

@@ -62,7 +62,7 @@ module:hook("muc-occupant-pre-join", function(event)
module:log("debug", "Bypass lobby invitee %s", occupant_jid)
occupant.role = "participant";
room:set_affiliation(true, jid_bare(occupant_jid), "member")
room:save();
room:save_occupant(occupant);
end
-- bypass password on the flip device
local join = stanza:get_child("x", MUC_NS);
@@ -111,12 +111,19 @@ end)
module:hook("muc-occupant-joined", function(event)
local room, occupant = event.room, event.occupant;
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
return ;
return;
end
if room._data.flip_participant_nick and occupant.nick == room._data.flip_participant_nick then
-- make joining participant from flip device have the same role and affiliation as for the previous device
local kicked_occupant = room:get_occupant_by_nick(room._data.kicked_participant_nick);
if not kicked_occupant then
module:log("info", "Kick participant not found, nick %s from main room jid %s",
room._data.kicked_participant_nick, room.jid)
return;
end
local initial_affiliation = room:get_affiliation(kicked_occupant.jid) or "member";
module:log("debug", "Transfer affiliation %s to occupant jid %s", initial_affiliation, occupant.jid)
room:set_affiliation(true, occupant.bare_jid, initial_affiliation)
@@ -129,7 +136,7 @@ module:hook("muc-occupant-joined", function(event)
local kicked_participant_node_jid = jid.split(kicked_occupant.jid);
module:log("info", "Kick participant jid %s nick %s from main room jid %s", kicked_occupant.jid, room._data.kicked_participant_nick, room.jid)
room:set_role(true, room._data.kicked_participant_nick, 'none')
room:save()
room:save_occupant(occupant);
-- Kick participant from the first device from the lobby room
if room._data.lobbyroom then
local lobby_room_jid = room._data.lobbyroom;

View File

@@ -165,6 +165,12 @@ function filter_stanza(stanza)
-- allow messages to or from moderator
local lobby_room_jid = jid_bare(stanza.attr.from);
local lobby_room = lobby_muc_service.get_room_from_jid(lobby_room_jid);
if not lobby_room then
module:log('warn', 'No lobby room found %s', stanza.attr.from);
return nil;
end
local is_to_moderator = lobby_room:get_affiliation(stanza.attr.to) == 'owner';
local from_occupant = lobby_room:get_occupant_by_nick(stanza.attr.from);
@@ -396,7 +402,7 @@ process_host_module(main_muc_component_config, function(host_module, host)
if not affiliation or affiliation == 'none' or affiliation == 'member' then
occupant.role = 'participant';
room:set_affiliation(true, invitee_bare_jid, 'member');
room:save();
room:save_occupant(occupant);
return;
end

View File

@@ -3,13 +3,15 @@
local queue = require "util.queue";
local new_throttle = require "util.throttle".create;
local timer = require "util.timer";
local st = require "util.stanza";
-- we max to 500 participants per meeting so this should be enough, we are not suppose to handle all
-- participants in one meeting
local PRESENCE_QUEUE_MAX_SIZE = 1000;
-- default to 5 participants per second
local join_rate_per_conference = module:get_option_number("muc_rate_joins", 5);
-- default to 3 participants per second
local join_rate_per_conference = module:get_option_number("muc_rate_joins", 3);
local leave_rate_per_conference = module:get_option_number("muc_rate_leaves", 5);
-- Measure/monitor the room rate limiting queue
local measure = require "core.statsmanager".measure;
@@ -31,16 +33,20 @@ local stat_longest_queue = 0;
-- Adds item to the queue
-- @returns false if queue is full and item was not added, true otherwise
local function add_item_to_queue(joining_queue, item, room, from)
if not joining_queue:push(item) then
module:log('error', 'Error pushing presence in queue for %s in %s', from, room.jid);
local function add_item_to_queue(queue, item, room, from, send_stats)
if not queue:push(item) then
module:log('error',
'Error pushing item in %s queue for %s in %s', send_stats and 'join' or 'leave', from, room.jid);
if send_stats then
measure_full_queue();
end
measure_full_queue();
return false;
else
-- check is this the longest queue and if so throws a stat
if joining_queue:count() > stat_longest_queue then
stat_longest_queue = joining_queue:count();
if send_stats and queue:count() > stat_longest_queue then
stat_longest_queue = queue:count();
measure_longest_queue(stat_longest_queue);
end
@@ -50,27 +56,23 @@ end
-- process join_rate_presence_queue in the room and pops element passing them to handle_normal_presence
-- returns 1 if we want to reschedule it after 1 second
local function timer_process_queue_elements (room)
local presence_queue = room.join_rate_presence_queue;
if not presence_queue or presence_queue:count() == 0 then
local function timer_process_queue_elements (rate, queue, process, queue_empty_cb)
if not queue or queue:count() == 0 then
return;
end
for _ = 1, join_rate_per_conference do
local ev = presence_queue:pop();
for _ = 1, rate do
local ev = queue:pop();
if ev then
-- we mark what we pass here so we can skip it on the next muc-occupant-pre-join event
ev.stanza.delayed_join_skip = true;
room:handle_normal_presence(ev.origin, ev.stanza);
process(ev);
end
end
-- if there are elements left, schedule an execution in a second
if presence_queue:count() > 0 then
if queue:count() > 0 then
return 1;
else
room.join_rate_queue_timer = false;
queue_empty_cb();
end
end
@@ -99,17 +101,28 @@ module:hook("muc-occupant-pre-join", function (event)
room.join_rate_presence_queue = queue.new(PRESENCE_QUEUE_MAX_SIZE);
end
if not add_item_to_queue(room.join_rate_presence_queue, event, room, stanza.attr.from) then
if not add_item_to_queue(room.join_rate_presence_queue, event, room, stanza.attr.from, true) then
-- let's not stop processing the event
return nil;
end
if not room.join_rate_queue_timer then
timer.add_task(1, function ()
local status, result = pcall(timer_process_queue_elements, room);
local status, result = pcall(timer_process_queue_elements,
join_rate_per_conference,
room.join_rate_presence_queue,
function(ev)
-- we mark what we pass here so we can skip it on the next muc-occupant-pre-join event
ev.stanza.delayed_join_skip = true;
room:handle_normal_presence(ev.origin, ev.stanza);
end,
function() -- empty callback
room.join_rate_queue_timer = false;
end
);
if not status then
-- there was an error in the timer function
module:log('error', 'Error processing queue: %s', result);
module:log('error', 'Error processing join queue: %s', result);
measure_errors_processing_queue();
@@ -130,7 +143,7 @@ module:hook("muc-occupant-pre-join", function (event)
-- if add fails as queue is full we return false and the event will continue processing, we risk re-order
-- but not losing it
return add_item_to_queue(room.join_rate_presence_queue, event, room, stanza.attr.from);
return add_item_to_queue(room.join_rate_presence_queue, event, room, stanza.attr.from, true);
end
end, 9); -- as we will rate limit joins we need to be the first to execute
@@ -141,3 +154,73 @@ end, 9); -- as we will rate limit joins we need to be the first to execute
module:hook('muc-room-destroyed',function(event)
event.room.join_rate_presence_queue = nil;
end);
module:hook('muc-occupant-pre-leave', function (event)
local occupant, room, stanza = event.occupant, event.room, event.stanza;
local throttle = room.leave_rate_throttle;
if not throttle then
throttle = new_throttle(leave_rate_per_conference, 1); -- rate per one second
room.leave_rate_throttle = throttle;
end
if not throttle:poll(1) then
if not room.leave_rate_presence_queue then
room.leave_rate_presence_queue = queue.new(PRESENCE_QUEUE_MAX_SIZE);
end
-- we need it later when processing the event
event.orig_role = occupant.role;
if not add_item_to_queue(room.leave_rate_presence_queue, event, room, stanza.attr.from, false) then
-- let's not stop processing the event
return nil;
end
-- set role to nil so the occupant will be removed from room occupants when we save it
-- we remove occupant from the list early on batches so we can spare sending few presences
occupant.role = nil;
room:save_occupant(occupant);
if not room.leave_rate_queue_timer then
timer.add_task(1, function ()
local status, result = pcall(timer_process_queue_elements,
leave_rate_per_conference,
room.leave_rate_presence_queue,
function(ev)
local occupant, orig_role, origin, room, stanza
= ev.occupant, ev.orig_role, ev.origin, ev.room, ev.stanza;
room:publicise_occupant_status(
occupant,
st.stanza("x", {xmlns = "http://jabber.org/protocol/muc#user";}),
nil, nil, nil, orig_role);
module:fire_event("muc-occupant-left", {
room = room;
nick = occupant.nick;
occupant = occupant;
origin = origin;
stanza = stanza;
});
end,
function() -- empty callback
room.leave_rate_queue_timer = false;
end
);
if not status then
-- there was an error in the timer function
module:log('error', 'Error processing leave queue: %s', result);
-- let's re-schedule timer so we do not lose the queue
return 1;
end
return result;
end);
room.leave_rate_queue_timer = true;
end
return true; -- we stop execution, so we do not process this leave at the moment
end
end);

View File

@@ -4,5 +4,7 @@
local COMPONENT_IDENTITY_TYPE = 'room_metadata';
local room_metadata_component_host = module:get_option_string('room_metadata_component', 'metadata.'..module.host);
module:depends("jitsi_session");
-- Advertise the component so clients can pick up the address and use it
module:add_identity('component', COMPONENT_IDENTITY_TYPE, room_metadata_component_host);

View File

@@ -35,15 +35,16 @@ end
-- Searches all rooms in the main muc component that holds a breakout room
-- caches it if found so we don't search it again
-- we should not cache objects in _data as this is being serialized when calling room:save()
local function get_main_room(breakout_room)
if breakout_room._data and breakout_room._data.main_room then
return breakout_room._data.main_room;
if breakout_room.main_room then
return breakout_room.main_room;
end
-- let's search all rooms to find the main room
for room in main_muc_service.each_room() do
if room._data and room._data.breakout_rooms_active and room._data.breakout_rooms[breakout_room.jid] then
breakout_room._data.main_room = room;
breakout_room.main_room = room;
return room;
end
end
@@ -117,7 +118,7 @@ function on_message(event)
local from = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(from);
if not occupant then
if not occupant or not room.speakerStats[occupant.jid] then
module:log("warn", "No occupant %s found for %s", from, roomAddress);
return false;
end

View File

@@ -17,6 +17,7 @@ local starts_with = main_util.starts_with;
local cjson_safe = require 'cjson.safe'
local timer = require "util.timer";
local async = require "util.async";
local inspect = require 'inspect';
local nr_retries = 3;
local ssl = require "ssl";
@@ -344,7 +345,11 @@ function Util:verify_room(session, room_address)
local auth_room = session.jitsi_meet_room;
if auth_room then
auth_room = string.lower(auth_room);
if type(auth_room) == 'string' then
auth_room = string.lower(auth_room);
else
module:log('warn', 'session.jitsi_meet_room not string: %s', inspect(auth_room));
end
end
if not self.enableDomainVerification then
-- if auth_room is missing, this means user is anonymous (no token for