Compare commits

...

28 Commits

Author SHA1 Message Date
Hristo Terezov
f43020f650 fix(gumPending): Handle ignored muted tracks. 2024-02-20 16:54:05 -06:00
Hristo Terezov
ba95ea3ac8 feat(conference): Add init debug log 2024-02-20 09:42:07 -06:00
Besnik Bleta
7dcd9c21bc fix(lang) update for sq translation (#14387) 2024-02-20 15:23:14 +01:00
damencho
990d21038e fix: Fixes initializing cached keys. 2024-02-16 11:36:07 -06:00
Mihaela Dumitru
a4d53f271f fix(ui) center conference info (#14379) 2024-02-16 14:37:12 +02:00
bgrozev
2a1f472873 Add a second shared secret to allow rotation if necessary. (#14378) 2024-02-15 15:00:30 -08:00
damencho
8679119677 fix: Change whitelist to support only domains. 2024-02-15 16:36:37 -06:00
damencho
b02c072ba7 fix: Handles few more cases for updating cached tokens. 2024-02-15 12:20:02 -06:00
Saúl Ibarra Corretgé
e8317fccfe fix(local-recordings) fix on Chrome
WebM + VP8 seems to be the one thing that works, requesting H.264 starts
the recorder in Matroska + AV1 format (WAT) but no data is received at
all.
2024-02-15 17:49:45 +01:00
Saúl Ibarra Corretgé
bbc9c64978 fix(recording) only show advanced section for cloud recordings 2024-02-15 17:49:45 +01:00
Saúl Ibarra Corretgé
8d2b8bc772 fix(recording) fix checking if we can skip the recording dialog 2024-02-15 15:15:38 +01:00
damencho
52c2911350 feat: Adds stats around room token validation. 2024-02-15 07:13:12 -06:00
damencho
a1ebcd559b feat: Adds a print for timing out http requests after the retry. 2024-02-15 07:13:12 -06:00
damencho
5bc47ec16a feat: Adds stat when a rayo IQ is filtered. 2024-02-15 07:13:12 -06:00
damencho
e1ac7d1609 feat: Adds stats around token validation. 2024-02-15 07:13:12 -06:00
damencho
8a596f1ba2 fix: Drops unused token validation. 2024-02-15 07:13:12 -06:00
damencho
38e7c65836 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1776.0.0+f5f8a137...v1777.0.0+3898d7aa
2024-02-14 22:52:49 -05:00
Jaya Allamsetty
9602a939d8 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1775.0.0+c7495280...v1776.0.0+f5f8a137
2024-02-14 12:32:39 -05:00
Calinteodor
07b01b1371 feat(conference/recordin): Fix always on labels (#14368)
* feat(conference/recording): condition live stream label visibility and fix labels tooltip translations
2024-02-14 13:11:57 +02:00
damencho
a1549086aa chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1773.0.0+cc9b0e60...v1775.0.0+c7495280
2024-02-13 14:02:28 -06:00
Saúl Ibarra Corretgé
7e0b00ba5f feat(prejoin) default to enabled (#14236)
* feat(prejoin) default to enabled

* squash: Fixes is prejoin config check.

---------

Co-authored-by: damencho <damencho@jitsi.org>
2024-02-13 12:47:59 -06:00
Calin-Teodor
3016853d81 feat(android): replaced NotificationChannels with NotificationUtils 2024-02-13 20:03:36 +02:00
Calinteodor
ead27ace30 feat(android): update media projection notification build (#14365)
* feat(android): update media projection notification build
2024-02-13 19:25:46 +02:00
Дамян Минков
067bb653e6 feat(лоббъ): Admin set function for multiple items.
* squash: Initial copy of the function.

* squash: Invokes the admin set function.

* feat: Admin set function for multiple items.
2024-02-13 10:29:41 -06:00
Calinteodor
ba20fc71a8 feat(android): separate MediaProjection and OngoingConference notifications (#14363)
* feat(android): separate MediaProjection and OngoingConference notifications
2024-02-13 17:54:01 +02:00
Mihaela Dumitru
a7b2726ebe fix(prejoin) adjust recording warning (#14361) 2024-02-13 13:12:10 +02:00
Calinteodor
a98eef7eb3 feat(android): fixed screen sharing for Android 33 (#14359)
* feat(android): fixed screen sharing for Android 33
2024-02-13 12:33:37 +02:00
damencho
895afbab65 fix: Fixes polls parsing. 2024-02-12 13:21:06 -06:00
44 changed files with 636 additions and 306 deletions

View File

@@ -19,9 +19,9 @@ buildscript {
ext {
kotlinVersion = "1.7.0"
buildToolsVersion = "33.0.2"
compileSdkVersion = 34
compileSdkVersion = 33
minSdkVersion = 24
targetSdkVersion = 34
targetSdkVersion = 33
supportLibVersion = "28.0.0"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.

View File

@@ -1,5 +1,6 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.content.Context;
import androidx.annotation.NonNull;
@@ -23,8 +24,9 @@ class JitsiMeetMediaProjectionModule
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
Activity currentActivity = getCurrentActivity();
JitsiMeetMediaProjectionService.launch(context);
JitsiMeetMediaProjectionService.launch(context, currentActivity);
}
@ReactMethod

View File

@@ -17,6 +17,7 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
@@ -28,6 +29,7 @@ import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
@@ -39,8 +41,11 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
public class JitsiMeetMediaProjectionService extends Service {
private static final String TAG = JitsiMeetMediaProjectionService.class.getSimpleName();
public static void launch(Context context) {
OngoingNotification.createOngoingConferenceNotificationChannel();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context, Activity currentActivity) {
NotificationUtils.createNotificationChannel(currentActivity);
Intent intent = new Intent(context, JitsiMeetMediaProjectionService.class);
@@ -55,12 +60,12 @@ public class JitsiMeetMediaProjectionService extends Service {
} 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);
JitsiMeetLogger.w(TAG + "Media projection service not started", e);
return;
}
if (componentName == null) {
JitsiMeetLogger.w(TAG + " Ongoing conference service not started");
JitsiMeetLogger.w(TAG + "Media projection service not started");
}
}
@@ -69,24 +74,6 @@ public class JitsiMeetMediaProjectionService extends Service {
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(null);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
@@ -95,6 +82,21 @@ public class JitsiMeetMediaProjectionService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification notification = MediaProjectionNotification.buildMediaProjectionNotification(this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
return START_NOT_STICKY;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(NOTIFICATION_ID, notification);
}
return START_NOT_STICKY;
}
}

View File

@@ -16,6 +16,7 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
@@ -33,6 +34,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.HashMap;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
@@ -52,8 +54,12 @@ public class JitsiMeetOngoingConferenceService extends Service
private boolean isAudioMuted;
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context, HashMap<String, Object> extraData) {
OngoingNotification.createOngoingConferenceNotificationChannel();
NotificationUtils.createNotificationChannel((Activity) context);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
@@ -90,15 +96,15 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(OngoingNotification.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(OngoingNotification.NOTIFICATION_ID, notification);
startForeground(NOTIFICATION_ID, notification);
}
}
@@ -130,13 +136,13 @@ public class JitsiMeetOngoingConferenceService extends Service
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
@@ -216,13 +222,13 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public void onReceive(Context context, Intent intent) {
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");
} else {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(OngoingNotification.NOTIFICATION_ID, notification);
notificationManager.notify(NOTIFICATION_ID, notification);
JitsiMeetLogger.i(TAG + " audio muted changed");
}

View File

@@ -0,0 +1,58 @@
/*
* 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 static org.jitsi.meet.sdk.NotificationUtils.ONGOING_CONFERENCE_CHANNEL_ID;
import android.app.Notification;
import android.content.Context;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Helper class for creating the media projection notification which is used with
* {@link JitsiMeetMediaProjectionService}.
*/
class MediaProjectionNotification {
private static final String TAG = MediaProjectionNotification.class.getSimpleName();
static Notification buildMediaProjectionNotification(Context context) {
if (context == null) {
JitsiMeetLogger.d(TAG, " Cannot create notification: no current context");
return null;
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ONGOING_CONFERENCE_CHANNEL_ID);
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.media_projection_notification_title))
.setContentText(context.getString(R.string.media_projection_notification_text))
.setPriority(NotificationCompat.PRIORITY_LOW)
.setOngoing(false)
.setUsesChronometer(false)
.setAutoCancel(true)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
return builder.build();
}
}

View File

@@ -1,10 +0,0 @@
package org.jitsi.meet.sdk;
import java.util.ArrayList;
import java.util.List;
public class NotificationChannels {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
}

View File

@@ -0,0 +1,50 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.List;
class NotificationUtils {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
public static List<String> allIds = new ArrayList<String>() {{ add(ONGOING_CONFERENCE_CHANNEL_ID); }};
private static final String TAG = NotificationUtils.class.getSimpleName();
static void createNotificationChannel(Activity context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
}

View File

@@ -16,23 +16,18 @@
package org.jitsi.meet.sdk;
import static org.jitsi.meet.sdk.NotificationChannels.ONGOING_CONFERENCE_CHANNEL_ID;
import static org.jitsi.meet.sdk.NotificationUtils.ONGOING_CONFERENCE_CHANNEL_ID;
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
@@ -41,40 +36,10 @@ import java.util.Random;
class OngoingNotification {
private static final String TAG = OngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static long startingTime = 0;
static void createOngoingConferenceNotificationChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context) {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel(ONGOING_CONFERENCE_CHANNEL_ID, context.getString(R.string.ongoing_notification_action_unmute), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Boolean isMuted) {
Context context = ReactInstanceManagerHolder.getCurrentActivity();
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
@@ -92,7 +57,7 @@ class OngoingNotification {
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(isMuted != null ? context.getString(R.string.ongoing_notification_text) : context.getString(R.string.ongoing_notification_action_screenshare))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent)
.setOngoing(true)
@@ -103,10 +68,6 @@ class OngoingNotification {
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
if (isMuted == null) {
return builder.build();
}
NotificationCompat.Action hangupAction = createAction(context, JitsiMeetOngoingConferenceService.Action.HANGUP, R.string.ongoing_notification_action_hang_up);
JitsiMeetOngoingConferenceService.Action toggleAudioAction = isMuted

View File

@@ -1,11 +1,12 @@
<resources>
<string name="app_name">Jitsi Meet SDK</string>
<string name="dropbox_app_key"></string>
<string name="media_projection_notification_title">Media projection</string>
<string name="media_projection_notification_text">You are currently sharing your screen.</string>
<string name="ongoing_notification_title">Ongoing meeting</string>
<string name="ongoing_notification_text">You are currently in a meeting. Tap to return to it.</string>
<string name="ongoing_notification_action_hang_up">Hang up</string>
<string name="ongoing_notification_action_mute">Mute</string>
<string name="ongoing_notification_action_screenshare">You are currently screen-sharing. Tap to return to the meeting.</string>
<string name="ongoing_notification_action_unmute">Unmute</string>
<string name="ongoing_notification_channel_name">Ongoing Conference Notifications</string>
</resources>

View File

@@ -91,7 +91,7 @@ import {
setVideoMuted,
setVideoUnmutePermissions
} from './react/features/base/media/actions';
import { MEDIA_TYPE } from './react/features/base/media/constants';
import { MEDIA_TYPE, VIDEO_TYPE } from './react/features/base/media/constants';
import {
getStartWithAudioMuted,
getStartWithVideoMuted,
@@ -405,7 +405,13 @@ function disconnect() {
* @returns {void}
*/
function setGUMPendingStateOnFailedTracks(tracks) {
const tracksTypes = tracks.map(track => track.getType());
const tracksTypes = tracks.map(track => {
if (track.getVideoType() === VIDEO_TYPE.DESKTOP) {
return MEDIA_TYPE.SCREENSHARE;
}
return track.getType();
});
const nonPendingTracks = [ MEDIA_TYPE.AUDIO, MEDIA_TYPE.VIDEO ].filter(type => !tracksTypes.includes(type));
APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
@@ -679,6 +685,8 @@ export default {
startWithVideoMuted: getStartWithVideoMuted(state) || isUserInteractionRequiredForUnmute(state)
};
logger.debug(`Executed conference.init with roomName: ${roomName}`);
this.roomName = roomName;
try {
@@ -1253,7 +1261,24 @@ export default {
room = APP.connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
// Filter out the tracks that are muted (except on Safari).
const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
let tracks = localTracks;
if (!browser.isWebKitBased()) {
const mutedTrackTypes = [];
tracks = localTracks.filter(track => {
if (!track.isMuted()) {
return true;
}
if (track.getVideoType() !== VIDEO_TYPE.DESKTOP) {
mutedTrackTypes.push(track.getType());
}
return false;
});
APP.store.dispatch(gumPending(mutedTrackTypes, IGUMPendingState.NONE));
}
this._setLocalAudioVideoStreams(tracks);
this._room = room; // FIXME do not use this

View File

@@ -723,7 +723,7 @@ var config = {
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
// // This replaces `prejoinPageEnabled`.
// // This replaces `prejoinPageEnabled`. Defaults to true.
// enabled: true,
// // Hides the participant name editing field in the prejoin screen.
// // If requireDisplayName is also set as true, a name should still be provided through

View File

@@ -41,7 +41,6 @@
position: absolute;
top: 0;
height: 48px;
max-width: calc(100% - 24px);
}
@keyframes hideSubject {

View File

@@ -1,5 +1,8 @@
{
"addPeople": {
"accessibilityLabel": {
"meetingLink": "Lidhje takimi: {{url}}"
},
"add": "Ftoni",
"addContacts": "Ftoni kontaktet tuaja",
"contacts": "kontakte",
@@ -39,6 +42,18 @@
"audioOnly": {
"audioOnly": "Gjerësi e ulët bande"
},
"bandwidthSettings": {
"assumedBandwidthBps": "p.sh. 10000000 për 10 Mbps",
"assumedBandwidthBpsWarning": "Vlera më të mëdha mund të shkaktojnë probleme rrjeti.",
"customValue": "vlerë vetjake",
"customValueEffect": "për të ujdisur vlerën e tanishme bps",
"leaveEmpty": "lëreni të zbrazët",
"leaveEmptyEffect": "për të lejuar kryerje vlerësimesh",
"possibleValues": "Vlera të mundshme",
"setAssumedBandwidthBps": "Gjerësi bande e supozuar (bps)",
"title": "Rregullime gjerësi bande",
"zeroEffect": "për të çaktivizuar videon"
},
"breakoutRooms": {
"actions": {
"add": "Shtoni aneks konsultimesh",
@@ -48,15 +63,22 @@
"leaveBreakoutRoom": "Dilni nga aneks konsultimesh",
"more": "Më tepër",
"remove": "Hiqe",
"rename": "Riemërtojeni",
"renameBreakoutRoom": "Riemërtoni aneks konsultimesh",
"sendToBreakoutRoom": "Dërgoje pjesëmarrësin te:"
},
"breakoutList": "Listë aneksesh",
"buttonLabel": "Dhoma aneks konsultimesh",
"defaultName": "Dhomë aneks konsultimesh #{{index}}",
"hideParticipantList": "Fshihe listën e pjesëmarrësve",
"mainRoom": "Dhoma kryesore",
"notifications": {
"joined": "Po hyhet te dhomë aneks konsultimesh \"{{name}}\"",
"joinedMainRoom": "Po hyhet te dhoma kryesore",
"joinedTitle": "Dhoma Aneks Konsultimesh"
}
},
"showParticipantList": "Shfaq listë pjesëmarrësish",
"title": "Dhoma Aneks Konsultimesh"
},
"calendarSync": {
"addMeetingURL": "Shtoni një lidhje takimi",
@@ -68,9 +90,9 @@
},
"join": "Merrni pjesë",
"joinTooltip": "Merrni pjesë në takim",
"nextMeeting": "takimi pasues",
"nextMeeting": "Takimi pasues",
"noEvents": "S’ka veprimtari të ardhshme të vëna në plan.",
"ongoingMeeting": "takim në zhvillim e sipër",
"ongoingMeeting": "Takim në zhvillim e sipër",
"permissionButton": "Hapni rregullimet",
"permissionMessage": "Që të shihni në aplikacion takimet tuaja, janë të domosdoshme lejet mbi Kalendarin.",
"refresh": "Rifresko kalendarin",
@@ -156,6 +178,7 @@
"localport_plural": "Porta vendore:",
"maxEnabledResolution": "maksimum dërgimi",
"more": "Shfaq më tepër",
"no": "jo",
"packetloss": "Humbje paketesh:",
"participant_id": "ID pjesëmarrësi:",
"quality": {
@@ -174,7 +197,8 @@
"status": "Lidhje:",
"transport": "Transport:",
"transport_plural": "Transporte:",
"video_ssrc": "SSRC Videoje:"
"video_ssrc": "Video SSRC:",
"yes": "po"
},
"dateUtils": {
"earlier": "Më herët",
@@ -195,7 +219,9 @@
"joinInBrowser": "Hyni që nga shfletues",
"launchMeetingLabel": "Si doni të hyhet në këtë takim?",
"launchWebButton": "Nise në web",
"noDesktopApp": "S’e keni aplikacionin?",
"noMobileApp": "S’e keni aplikacionin?",
"or": "OR",
"termsAndConditions": "Duke vazhduar, pajtoheni me <a href='{{termsAndConditionsLink}}' rel='noopener noreferrer' target='_blank'>terms & conditions.</a> tona",
"title": "Po niset takimi juaj në {{app}}…",
"titleNew": "Po niset takimi juaj…",
@@ -220,7 +246,7 @@
"noPermission": "S’u akordua leje",
"previewUnavailable": "S’bëhet dot paraparje",
"selectADevice": "Përzgjidhni një pajisje",
"testAudio": "Luaj një tingull, për provë"
"testAudio": "Testojeni"
},
"dialIn": {
"screenTitle": "Përmbledhje rënieje numrash"
@@ -231,21 +257,28 @@
"dialog": {
"Back": "Mbrapsht",
"Cancel": "Anuloje",
"IamHost": "Jam organizatori",
"IamHost": "Hyni",
"Ok": "OK",
"Remove": "Hiqe",
"Share": "Ndaje",
"Submit": "Parashtroje",
"WaitForHostMsg": "Konferenca s’ka nisur ende. Nëse jeni organizatori, ju lutemi, bëni mirëfilltësimin. Përndryshe, ju lutemi, pritni që të mbërrijë organizatori.",
"WaitingForHostTitle": "Po pritet për organizatorin…",
"WaitForHostMsg": "Konferenca s’ka nisur, ngaqë s’ka mbërritur ende ndonjë moderator. Nëse dëshironi të bëheni një moderator, ju lutemi, bëni hyrjen. Përndryshe, ju lutemi, pritni.",
"WaitingForHostButton": "Prit për moderator",
"WaitingForHostTitle": "Po pritet për një moderator…",
"Yes": "Po",
"accessibilityLabel": {
"liveStreaming": "Transmetim i Drejtpërdrejtë"
"Cancel": "Anuloje (dilni nga dialogu)",
"Ok": "OK (ruajeni dhe dilni nga dialogu)",
"close": "Mbylle dialogun",
"liveStreaming": "Transmetim i Drejtpërdrejtë",
"sharingTabs": "Mundësi ndarjeje me të tjerë"
},
"add": "Shtoni",
"addMeetingNote": "Shtoni një shënim rreth këtij takimi",
"addOptionalNote": "Shtoni një shënim (në daçi):",
"allow": "Lejoje",
"allowToggleCameraDialog": "A e lejoni {{initiatorName}} të ndryshojë anën nga sheh kamera juaj?",
"allowToggleCameraTitle": "Të lejohet ndryshimi i kamerës?",
"alreadySharedVideoMsg": "Një tjetër pjesëmarrës po ndan me të tjerët një video. Kjo konferencë lejon vetëm një ndarje videoje në herë.",
"alreadySharedVideoTitle": "Lejohet vetëm një ndarje videoje me të tjerët në herë",
"applicationWindow": "Dritare aplikacioni",
@@ -306,7 +339,8 @@
"lockRoom": "Shtoni takim $t(lockRoomPasswordUppercase)",
"lockTitle": "Kyçja dështoi",
"login": "Hyrje",
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të ndalet konferenca?",
"loginQuestion": "Jeni i sigurt se doni të hyhet dhe të braktiset konferenca?",
"logoutQuestion": "Jeni i sigurt se doni të dilet dhe të braktiset konferenca?",
"logoutTitle": "Dalje",
"maxUsersLimitReached": "U mbërrit në kufi numri maksimum pjesëmarrësish. Konferenca është plot. Ju lutemi, lidhuni me të zotin e takimit, ose provoni më vonë!",
"maxUsersLimitReachedTitle": "U mbërrit në kufi numri maksimum pjesëmarrësish",
@@ -334,7 +368,7 @@
"muteEveryonesVideoTitle": "Të ndalet videoja e gjithkujt?",
"muteParticipantBody": "S’do jeni në gjendje të hiqni heshtimin për ta, por ata munden kurdo ta heqin për veten.",
"muteParticipantButton": "Heshtoje",
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktvizojnë për veten.",
"muteParticipantsVideoBody": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre, por ata munden kurdo ta riaktivizojnë për veten.",
"muteParticipantsVideoBodyModerationOn": "S’do të jeni në gjendje të riaktivizoni kamerën e tyre dhe as ata s’do të munden.",
"muteParticipantsVideoButton": "Ndale videon",
"muteParticipantsVideoDialog": "Jeni i sigurt se doni të çaktivizoni kamerën e këtij pjesëmarrësi? S’do të jeni në gjendje të riaktivizoni kamerën, por ai mund ta riaktivizojë kurdo.",
@@ -349,8 +383,6 @@
"permissionCameraRequiredError": "Leja mbi kamerën është e domosdoshme për të marrë pjesë në konferenca me video. Ju lutemi, akordojeni që nga Rregullimet",
"permissionErrorTitle": "Leje e domosdoshme",
"permissionMicRequiredError": "Leja mbi mikrofonin është e domosdoshme për të marrë pjesë në konferenca me audio. Ju lutemi, akordojeni që nga Rregullimet",
"popupError": "Shfletuesi juaj i bllokon dritaret flluskë prej këtij sajti. Ju lutemi, aktivizoni flluskat te rregullimet e sigurisë të shfletuesit tuaj dhe riprovoni.",
"popupErrorTitle": "Flluska u bllokua",
"readMore": "më tepër",
"recentlyUsedObjects": "Së fundi përdorët objekte",
"recording": "Regjistrim",
@@ -367,6 +399,8 @@
"removePassword": "Hiqe $t(lockRoomPassword)",
"removeSharedVideoMsg": "Jeni i sigurt se doni të hiqni videon që keni ndarë me të tjerë?",
"removeSharedVideoTitle": "Hiqe videon e ndarë me të tjerë",
"renameBreakoutRoomLabel": "Emër dhome",
"renameBreakoutRoomTitle": "Riemërtoni aneks konsultimesh",
"reservationError": "Gabim sistemi rezervimi",
"reservationErrorMsg": "Kod gabimi: {{code}}, mesazh: {{msg}}",
"retry": "Riprovoni",
@@ -386,8 +420,10 @@
"sendPrivateMessageTitle": "Të dërgohet privatisht?",
"serviceUnavailable": "Shërbim jashtë funksionimi",
"sessTerminated": "Thirrja përfundoi",
"sessTerminatedReason": "Takimi u përfundua",
"sessionRestarted": "Thirrja rinisi për shkak të një problemi lidhjeje.",
"shareAudio": "Vazhdo",
"shareAudio": "Vazhdoni",
"shareAudioAltText": "që të ndani me të tjerë lëndën e dëshiruar, kaloni te \"Skedë Shfletuesi\", përzgjidhni lëndën, aktivizoni shenjën për \"ndani audio me të tjerë\" dhe mandej klikoni butonin \"ndaje me të tjerë\"",
"shareAudioTitle": "Si të ndahet audio me të tjerë",
"shareAudioWarningD1": "lypset të ndalni tregim ekrani, para se të ndani audion tuaj me të tjerë.",
"shareAudioWarningD2": "lypset të rinisni tregimin e ekranit tuaj dhe t’i vini shenjë mundësisë “ndani audio me të tjerë”.",
@@ -417,7 +453,25 @@
"thankYou": "Faleminderit që përdorni {{appName}}!",
"token": "token",
"tokenAuthFailed": "Na ndjeni, s’keni leje të merrni pjesë në këtë thirrje.",
"tokenAuthFailedReason": {
"audInvalid": "Vlerë `aud` e pavlefshme. Duhet të jetë `jitsi`.",
"contextNotFound": "Prej ngarkesës mungon objekti `context`.",
"expInvalid": "Vlerë `exp` e pavlefshme.",
"featureInvalid": "Veçori e pavlefshme: {{feature}}, gjasat janë të mos jetë sendërtuar ende.",
"featureValueInvalid": "Vlerë e pavlefshme për veçorinë: {{feature}}.",
"featuresNotFound": "Prej ngarkesës mungon objekti `features`.",
"headerNotFound": "Mungon krye.",
"issInvalid": "Vlerë `iss` e pavlefshme. Duhet të jetë `chat`.",
"kidMismatch": "ID kyçi (kid) nuk përputhet me sub.",
"kidNotFound": "Mungon ID Kyçi (kid).",
"nbfFuture": "Vlera `nbf` gjendet në të ardhmen.",
"nbfInvalid": "Vlerë `nbf` e pavlefshme.",
"payloadNotFound": "Mungon ngarkesë.",
"tokenExpired": "Token-i ka skaduar."
},
"tokenAuthFailedTitle": "Mirëfilltësimi dështoi",
"tokenAuthFailedWithReasons": "Na ndjeni, nuk keni leje të merrni pjesë në këtë thirrje. Arsye e mundshme: {{reason}}",
"tokenAuthUnsupported": "Nuk mbulohet URL token-i.",
"transcribing": "Transkriptim",
"unlockRoom": "Hiq $t(lockRoomPassword) takimi",
"user": "Përdorues",
@@ -429,8 +483,12 @@
"verifyParticipantTitle": "Verifikim përdoruesi",
"videoLink": "Lidhje videoje",
"viewUpgradeOptions": "Shihni mundësi përmirësimi",
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transcriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
"viewUpgradeOptionsContent": "Që të përfitoni përdorim të pakufizuar veçorish me pagesë, të tilla si regjistrimi, transkriptime, RTMP Streaming & etj, duhet të përmirësoni planin tuaj.",
"viewUpgradeOptionsTitle": "Zbuluat një veçori me pagesë!",
"whiteboardLimitContent": "Na ndjeni, është mbërritur te kufiri i tabelave të njëkohshme.",
"whiteboardLimitReference": "Për më tepër hollësi, ju lutemi, vizitoni",
"whiteboardLimitReferenceUrl": "sajtin tonë",
"whiteboardLimitTitle": "Kufizim përdorimi tabele",
"yourEntireScreen": "Krejt ekranin tuaj"
},
"documentSharing": {
@@ -443,6 +501,9 @@
"title": "Trupëzojeni këtë takim"
},
"feedback": {
"accessibilityLabel": {
"yourChoice": "Zgjedhja juaj: {{rating}}"
},
"average": "Çka",
"bad": "I dobët",
"detailsLabel": "Na thoni më tepër rreth tij.",
@@ -499,13 +560,16 @@
"noNumbers": "S’ka numra për përdorim.",
"noPassword": "Asnjë",
"noRoom": "S’u dha dhomë për të cilën të formësohet numri.",
"noWhiteboard": "S’u ngarkua dot tabela.",
"numbers": "Numra Për T’u Përdorur",
"password": "$t(lockRoomPasswordUppercase): ",
"reachedLimit": "Keni mbërritur në kufijtë e planit tuaj.",
"sip": "Adresë SIP",
"sipAudioOnly": "Adresë SIP vetëm për audio",
"title": "Ndani me të tjerë",
"tooltip": "Ndani me të tjerë lidhje dhe hollësi numrash për këtë takim",
"upgradeOptions": "Ju lutemi, shihni mundësitë e përmirësimit, te"
"upgradeOptions": "Ju lutemi, shihni mundësitë e përmirësimit, te",
"whiteboardError": "Gabim në ngarkimin e tabelës. Ju lutemi, riprovoni më vonë."
},
"inlineDialogFailure": {
"msg": "Ngecëm pak.",
@@ -607,13 +671,13 @@
"knockingParticipantList": "Listë pjesëmarrësish që duan të hyjnë",
"lobbyChatStartedNotification": "{{moderator}} filloi një fjalosje në holl me {{attendee}}",
"lobbyChatStartedTitle": "{{moderator}} ka filluar një fjalosje në holl me ju.",
"lobbyClosed": "Dhoma holl u mbyll.",
"nameField": "Jepni emrin tuaj",
"notificationLobbyAccessDenied": "Hyrja e {{targetParticipantName}} është hedhur poshtë nga {{originParticipantName}}",
"notificationLobbyAccessGranted": "{{targetParticipantName}} është lejuar të hyjë nga {{originParticipantName}}",
"notificationLobbyDisabled": "Holli është çaktivizuar nga {{originParticipantName}}",
"notificationLobbyEnabled": "Holli është aktivizuar nga {{originParticipantName}}",
"notificationTitle": "Holl",
"passwordField": "Jepni fjalëkalim takimi",
"passwordJoinButton": "Hyni",
"title": "Holl",
"toggleLabel": "Aktivizoni hollin"
@@ -646,6 +710,8 @@
"sessionToken": "Token Sesioni",
"start": "Nis Regjistrim",
"stop": "Ndale Regjistrimin",
"stopping": "Po ndalet Regjistrimi",
"wait": "Ju lutemi, prisni, teksa ruajmë regjistrimin tuaj",
"yes": "Po"
},
"lockRoomPassword": "fjalëkalim",
@@ -667,8 +733,11 @@
"connectedTwoMembers": "{{first}} dhe {{second}} tjetër hynë në takim",
"dataChannelClosed": "Rënie cilësie videoje",
"dataChannelClosedDescription": "Kanali urë u shkëput, kështu që cilësia e videos është kufizuar te vlera më e ulët.",
"disabledIframe": "Trupëzimi është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash.",
"disabledIframeSecondary": "Trupëzimi i {{domain}} është menduar vetëm për qëllime demonstrimi, ndaj kjo thirrje do të ndërpritet pas {{timeout}} minutash. Ju lutemi, për trupëzime të njëmendta përdorni <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a>!",
"disconnected": "u shkëput",
"displayNotifications": "Shfaq njoftime për",
"dontRemindMe": "Mos ma kujto",
"focus": "Fokusi te konferenca",
"focusFail": "{{component}} jo i passhëm - riprovoni pas {{ms}} sekondash",
"gifsMenu": "GIPHY",
@@ -677,6 +746,7 @@
"invitedOneMember": "{{name}} u ftua",
"invitedThreePlusMembers": "{{name}} dhe {{count}} të tjerë u ftuan",
"invitedTwoMembers": "{{first}} dhe {{second}} u ftuan",
"joinMeeting": "Hyni",
"kickParticipant": "{{kicked}} u përzu nga {{kicker}}",
"leftOneMember": "{{name}} doli nga takimi",
"leftThreePlusMembers": "{{name}} dhe mjaft të tjerë dolën nga takimi",
@@ -711,7 +781,6 @@
"newDeviceCameraTitle": "U pikas kamerë e re",
"noiseSuppressionDesktopAudioDescription": "Mbytja e zhurmave s’mund të aktivizohet teksa ndahet me të tjerët audioja e desktopit, ju lutemi, çaktivizojeni dhe riprovoni.",
"noiseSuppressionFailedTitle": "S’u arrit të nisej mbytja e zhurmave",
"noiseSuppressionNoTrackDescription": "Ju lutemi, së pari, çheshtoni mikrofonin tuaj.",
"noiseSuppressionStereoDescription": "Aktualisht nuk mbulohet mbytje zhurmash audioje stereo.",
"oldElectronClientDescription1": "Duket se përdorni një version të vjetër të klientit Jitsi Meet, i cili ka cenueshmëri të ditura sigurie. Ju lutemi, siguroni përditësimin me ",
"oldElectronClientDescription2": "montimin tonë më të ri",
@@ -735,13 +804,18 @@
"startSilentTitle": "Hytë pa zë në dalje!",
"suboptimalBrowserWarning": "Kemi frikë se funksionimi i takimit për ju s’do të jetë kushedi këtu. Po kërkojmë rrugë për ta përmirësuar këtë punë, por deri atëherë, ju lutemi, provoni të përdorni një nga <a href='{{recommendedBrowserPageLink}}' target='_blank'>shfletuesit e mbuluar plotësisht</a>.",
"suboptimalExperienceTitle": "Sinjalizim Mbi Shfletuesin",
"suggestRecordingAction": "Niseni",
"suggestRecordingDescription": "Do të donit të nisej një regjistrim?",
"suggestRecordingTitle": "Regjistroje këtë takim",
"unmute": "Çheshtoje",
"videoMutedRemotelyDescription": "Mundeni përherë ta rihapni.",
"videoMutedRemotelyTitle": "Videoja juaj u mbyll nga {{moderator}}",
"videoUnmuteBlockedDescription": "Heqja e heshtimit të kamerës dhe veprimi i tregimit të desktopit janë bllokuar përkohësisht për shkak kufizimesh të sistemit.",
"videoUnmuteBlockedTitle": "Heqja e heshtimit të kamerës dhe tregimi i desktopit janë bllokuar!",
"viewLobby": "Shihni hollin",
"waitingParticipants": "{{waitingParticipants}} vetë"
"waitingParticipants": "{{waitingParticipants}} vetë",
"whiteboardLimitDescription": "Ju lutemi, ruani ç’keni bërë, ngaqë së shpejti do të mbërrihet në kufi përdoruesi dhe tabela do të mbyllet.",
"whiteboardLimitTitle": "Përdorim tabele"
},
"participantsPane": {
"actions": {
@@ -752,6 +826,7 @@
"askUnmute": "Kërkoni heqje heshtimi",
"audioModeration": "Heqin heshtim të vetes",
"blockEveryoneMicCamera": "Bllokoni mikrofonin dhe kamerën e gjithkujt",
"breakoutRooms": "Dhoma anekse konsultimesh",
"invite": "Ftoni Dikë",
"moreModerationActions": "Më tepër mundësi moderimi",
"moreModerationControls": "Më tepër kontrolle moderimi",
@@ -769,7 +844,8 @@
"headings": {
"lobby": "Holli ({{count}})",
"participantsList": "Pjesëmarrës në takim ({{count}})",
"visitors": "Vizitorë ({{count}})",
"visitorRequests": " (requests {{count}})",
"visitors": "Vizitorë {{count}}",
"waitingLobby": "Duke pritur në holl ({{count}})"
},
"search": "Kërkoni te pjesëmarrësit",
@@ -859,12 +935,15 @@
"joinWithoutAudio": "Merrni pjesë pa audio",
"keyboardShortcuts": "Aktivizo shkurtore tastiere",
"linkCopied": "Lidhja u kopjua në të papastër",
"lookGood": "Mikrofoni juaj po punon si duhet",
"lookGood": "Gjithçka po punon si duhet",
"or": "ose",
"premeeting": "Para takimit",
"proceedAnyway": "Vazhdo, sido qoftë",
"recordingWarning": "Këtë thirrje pjesëmarrës të tjerë mund ta regjistrojnë",
"screenSharingError": "Gabim ndarjeje ekrani me të tjerë:",
"showScreen": "Aktivizoni skenë para takimit",
"startWithPhone": "Nise me audio telefoni",
"unsafeRoomConsent": "I kuptoj rreziqet, dëshiroj të marr pjesë te takimi",
"videoOnlyError": "Gabim video:",
"videoTrackError": "S’u krijua dot pistë video.",
"viewAllNumbers": "shihni krejt numrat"
@@ -885,9 +964,9 @@
},
"profile": {
"avatar": "avatar",
"setDisplayNameLabel": "Caktoni emrin tuaj për në ekran",
"setDisplayNameLabel": "Emër",
"setEmailInput": "Jepni email",
"setEmailLabel": "Caktoni email të gravatarit tuaj",
"setEmailLabel": "Jepni email të gravatarit tuaj",
"title": "Profil"
},
"raisedHand": "Do të donte të fliste",
@@ -919,17 +998,16 @@
"failedToStart": "S’u arrit të niset regjistrimi",
"fileSharingdescription": "Ndajeni regjistrimin me pjesëmarrësit në takim",
"highlight": "Nxjerrje në pah",
"highlightMoment": "Nxirni në pah një çast",
"highlightMoment": "Nxirrni në pah një çast",
"highlightMomentDisabled": "Mund të nxirrni në pah çaste kur fillon regjistrimi",
"highlightMomentSuccess": "Çasti u nxorr në pah",
"highlightMomentSuccess": "Çasti u nxor në pah",
"highlightMomentSucessDescription": "Çasti i nxjerrë në pah nga ju do të shtohet te përmbledhja e takimit.",
"inProgress": "Regjistrim ose transmetim drejtpërsëdrejti në ecuri e sipër",
"limitNotificationDescriptionNative": "Për shkak kërkesash të shumta, regjistrimi juaj do të kufizohet në {{limit}} min. Për regjistrime të pakufizuara provoni <3>{{app}}</3>.",
"limitNotificationDescriptionWeb": "Për shkak kërkesash të shumta, regjistrimi juaj do të kufizohet në {{limit}} min. Për regjistrime të pakufizuara provoni <a href={{url}} rel='noopener noreferrer' target='_blank'>{{app}}</a>.",
"linkGenerated": "Kemi prodhuar një lidhje për te regjistrimi juaj.",
"live": "DREJTPËRSËDREJTI",
"localRecordingNoNotificationWarning": "Regjistrimi s’do t’u njoftohet pjesëmarrësve të tjerë. Do t’ju duhet t’i vini në dijeni se takimi po regjistrohet.",
"localRecordingNoVideo": "Videoja nuk po regjistrohet",
"localRecordingNoVideo": "Videoja s’po regjistrohet",
"localRecordingStartWarning": "Ju lutemi, sigurohuni se e ndalni regjistrimin para se dilni nga takimi, që të mund ta ruani.",
"localRecordingStartWarningTitle": "Ndaleni regjistrimin që ta ruani",
"localRecordingVideoStop": "Ndalja e videos tuaj do të ndalë gjithashtu edhe regjistrimin vendor. Jeni i sigurt se doni të vazhdohet?",
@@ -944,13 +1022,15 @@
"onBy": "{{name}} nisi regjistrimin",
"onlyRecordSelf": "Regjistro vetëm rrjedhat e mia audio dhe video",
"pending": "Po përgatitet të regjistrohet takimi…",
"rec": "REC",
"recordAudioAndVideo": "Regjistro audio dhe video",
"recordTranscription": "Regjistro transkriptimin",
"saveLocalRecording": "Ruajeni lokalisht kartelën e regjistrimit (Beta)",
"serviceDescription": "Regjistrimi juaj do të ruhet nga shërbimi i regjistrimit",
"serviceDescriptionCloud": "Regjistrim në re",
"serviceDescriptionCloudInfo": "Takimet e regjistruara spastrohen automatikisht 24h pas kohës së regjistrimit të tyre.",
"serviceName": "Shërbim regjistrimi",
"sessionAlreadyActive": "Ky sesion po regjistrohet ose transmetohet drejtpërsëdrejti tashmë.",
"showAdvancedOptions": "Mundësi të mëtejshme",
"signIn": "Hyni",
"signOut": "Dilni",
"surfaceError": "Ju lutemi, përdorni skedën e tanishme.",
@@ -966,10 +1046,17 @@
"security": {
"about": "Takimit tuaj mund t’i shtoni një $t(lockRoomPassword). Pjesëmarrësve do t’u duhet të japin $t(lockRoomPassword) përpara se të lejohen të marrin pjesë në takim.",
"aboutReadOnly": "Pjesëmarrësit moderatorë mund t’i shtojnë takimit një $t(lockRoomPassword). Pjesëmarrësve do t’u duhet të japin $t(lockRoomPassword) përpara se të lejohen të marrin pjesë në takim.",
"insecureRoomNameWarning": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të marrin pjesë në konferencën tuaj. Shihni mundësinë e bërjes të sigurt të takimit tuaj, duke përdorur butonin e sigurisë.",
"title": "Mundësi Sigurie"
"insecureRoomNameWarningNative": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të hyjnë në takimin tuaj. {{recommendAction}} Mësoni më tepër rreth sigurimit të takimit tuaj ",
"insecureRoomNameWarningWeb": "Emri i dhomës s’është pa rrezik. Pjesëmarrës të padëshiruar munden të hyjnë në takimin tuaj. {{recommendAction}} Mësoni më tepër rreth sigurimit të takimit tuaj <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">këtu</a>.",
"title": "Mundësi Sigurie",
"unsafeRoomActions": {
"meeting": "Shihni mundësinë e sigurimit të takimit tuaj, duke përdorur butonin e sigurisë.",
"prejoin": "Shihni mundësinë e përdorimit për takimin të një emri më të veçantë.",
"welcome": "Shihni mundësinë e përdorimit për takimin të një emri më të veçantë, ose zgjidhni një nga sugjerimet."
}
},
"settings": {
"audio": "Audio",
"buttonLabel": "Rregullime",
"calendar": {
"about": "Integrimi me kalendar {{appName}} përdoret për të hyrë me sukses te kalendari juaj, që kështu të mund të lexohen veprimtari të ardhshme.",
@@ -990,9 +1077,11 @@
"maxStageParticipants": "Numër maksimum pjesëmarrësish që mund të fiksohen te skena kryesore (EKSPERIMENTALe)",
"microphones": "Mikrofona",
"moderator": "Moderator",
"more": "Më tepër",
"moderatorOptions": "Mundësi moderatori",
"more": "Të përgjitshme",
"name": "Emër",
"noDevice": "Asnjë",
"notifications": "Njoftime",
"participantJoined": "Hyri Pjesëmarrës",
"participantKnocking": "Pjesëmarrës hyri në holl",
"participantLeft": "Doli Pjesëmarrës",
@@ -1003,13 +1092,14 @@
"selectCamera": "Kamerë",
"selectMic": "Mikrofon",
"selfView": "Parje e vetes",
"sounds": "Tinguj",
"shortcuts": "Shkurtore",
"speakers": "Altoparlantë",
"startAudioMuted": "Gjithkush fillon i heshtuar",
"startReactionsMuted": "Heshto tinguj reagimesh për këdo",
"startVideoMuted": "Gjithkush fillon i fshehur",
"talkWhileMuted": "Flisni, teksa jeni i heshtuar",
"title": "Rregullime"
"title": "Rregullime",
"video": "Video"
},
"settingsView": {
"advanced": "Të mëtejshme",
@@ -1017,6 +1107,7 @@
"alertOk": "OK",
"alertTitle": "Kujdes",
"alertURLText": "URL-ja e dhënë për shërbyesin është e pavlefshme",
"apply": "Aplikoji",
"buildInfoSection": "Hollësi Montimi",
"conferenceSection": "Konferencë",
"disableCallIntegration": "Çaktivizo integrim thirrje me sistemin",
@@ -1027,19 +1118,21 @@
"displayNamePlaceholderText": "P.sh.: Zamir Gjoli",
"email": "Email",
"emailPlaceholderText": "email@example.com",
"gavatarMessage": "Nëse emal-i juaj është i përshoqëruar me një llogari Gravatar, do ta përdorim për të shfaqur foton e profilit tuaj.",
"goTo": "Kalo te",
"header": "Rregullime",
"help": "Ndihmë",
"links": "Lidhje",
"privacy": "Privatësi",
"profileSection": "Profil",
"sdkVersion": "Version SDK",
"serverURL": "URL Shërbyesi",
"showAdvanced": "Shfaq rregullime të mëtejshme",
"startCarModeInLowBandwidthMode": "Nën mënyrën “gjerësi e ulët bande” nis mënyrën automjet",
"startWithAudioMuted": "Fillo me audio të mbyllur",
"startWithVideoMuted": "Fillo me video të mbyllur",
"terms": "Terma",
"version": "Version"
"version": "Version aplikacioni"
},
"share": {
"dialInfoText": "\n\n=====\n\nThjesht doni t’i bini numrit në telefonin tuaj?\n\n{{defaultDialInNumber}}Klikoni mbi këtë lidhje, që të shihni numra telefoni për këtë takim\n{{dialInfoPageUrl}}",
@@ -1050,7 +1143,7 @@
"angry": "I zemëruar",
"disgusted": "I pështirosur",
"displayEmotions": "Shfaq emocione",
"fearful": "I firkësuar",
"fearful": "I frikësuar",
"happy": "I gëzuar",
"hours": "{{count}}h",
"minutes": "{{count}}m",
@@ -1058,7 +1151,7 @@
"neutral": "Asnjanës",
"sad": "I trishtuar",
"search": "Kërko",
"searchHint": "Kërkoni pjesëmarrësit",
"searchHint": "Kërkoni për pjesëmarrës",
"seconds": "{{count}}s",
"speakerStats": "Statistika Folësi",
"speakerTime": "Kohë Folësi",
@@ -1081,7 +1174,7 @@
"toolbar": {
"Settings": "Rregullime",
"accessibilityLabel": {
"Settings": "Shfaq/Fshih rregullimet",
"Settings": "Hap rregullimet",
"audioOnly": "Hap/Mbyll vetëm audion",
"audioRoute": "Përzgjidhni pajisje zëri",
"boo": "Ya",
@@ -1091,11 +1184,20 @@
"cc": "Shfaq/Fshih titra",
"chat": "Hapni / Mbyllni fjalosje",
"clap": "Duartrokitje",
"closeChat": "Mbylle fjalosjen",
"closeMoreActions": "Mbyll menunë Më Tepër Veprime",
"closeParticipantsPane": "Mbyll kuadratin pjesëmarrës",
"collapse": "Tkurre",
"document": "Shfaq/Fshih dokument të ndarë",
"documentClose": "Mbyll dokument të ndarë",
"documentOpen": "Hap dokument të ndarë",
"download": "Shkarkoni aplikacionet tona",
"embedMeeting": "Trupëzoni takimin",
"endConference": "Përfundoje takimin për të tërë",
"enterFullScreen": "Shiheni sa krejt ekrani",
"enterTileView": "Kaloni nën mënyrën me kuadrate",
"exitFullScreen": "Dil nga mënyra “Sa krejt ekrani”",
"exitTileView": "Dil nga mënyra me kuadrate",
"expand": "Zgjeroje",
"feedback": "Lini përshtypje",
"fullScreen": "Kalo nën/Dil nga mënyra “Sa krejt ekrani”",
@@ -1104,6 +1206,7 @@
"hangup": "Braktiseni takimin",
"heading": "Panel",
"help": "Ndihmë",
"hideWhiteboard": "Fshihe tabelën",
"invite": "Ftoni njerëz",
"kick": "Përzëre pjesëmarrësin",
"laugh": "E qeshur",
@@ -1113,21 +1216,25 @@
"lobbyButton": "Aktivizo/Çaktivizoni mënyrën holl",
"localRecording": "Shfaq/Fshih kontrolle regjistrimi vendor",
"lockRoom": "Aktivizo/Çaktivizo fjalëkalim takimi",
"lowerHand": "Ulni dorën",
"moreActions": "Më tepër veprime",
"moreActionsMenu": "Menu “Më tepër veprime”",
"moreOptions": "Shfaq më tepër mundësi",
"mute": "Heshtoje / Çheshtoje",
"mute": "Heshto mikrofonin",
"muteEveryone": "Heshto gjithkënd",
"muteEveryoneElse": "Heshto gjithkënd tjetër",
"muteEveryoneElsesVideoStream": "Ndal videon e gjithkujt tjetër",
"muteEveryonesVideoStream": "Ndal videon e gjithkujt",
"muteGUMPending": "Po lidhet mikrofoni juaj",
"noiseSuppression": "Mbytje zhurmash",
"participants": "Pjesëmarrës",
"openChat": "Hapni fjalosje",
"participants": "Hapni kuadrat pjesëmarrësish",
"pip": "Aktivizo/Çaktivizo mënyrën “Picture-in-Picture”",
"privateMessage": "Dërgoni mesazh privat",
"profile": "Përpunoni profilin tuaj",
"raiseHand": "Ngrini / Ulni dorën tuaj",
"reactionsMenu": "Hap / Mbyll menu reagimesh",
"raiseHand": "Ngrini dorën",
"reactions": "Reagime",
"reactionsMenu": "Menu reagimesh",
"recording": "Nis/Ndal regjistrimin",
"remoteMute": "Heshto pjesëmarrësin",
"remoteVideoMute": "Çaktivizo kamerën e pjesëmarrësit",
@@ -1135,20 +1242,25 @@
"selectBackground": "Përzgjidhni Sfond",
"selfView": "Shfaq/Fshih pamje të vetes",
"shareRoom": "Ftoni dikë",
"shareYourScreen": "Nisni / Ndalni tregimin e ekranit tuaj",
"shareYourScreen": "Nisni tregimin e ekranit tuaj",
"shareaudio": "Ndani audio me të tjerë",
"sharedvideo": "Aktivizo/Çaktivizo ndarje videoje me të tjerë",
"sharedvideo": "Ndani video me të tjerë",
"shortcuts": "Shfaq/Fshih shkurtore",
"show": "Shfaqe në skenë",
"showWhiteboard": "Shfaq tabelë",
"silence": "Heshtje",
"speakerStats": "Shfaq/Fshih statistika folësi",
"stopScreenSharing": "Ndalni tregimin e ekranit tuaj",
"stopSharedVideo": "Ndalni videon",
"surprised": "I befasuar",
"tileView": "Kalo në/Dil nga mënyra mozaik",
"toggleCamera": "Hap/Mbyll kamerën",
"toggleFilmstrip": "Shfaq/Fshih shirit filmi",
"unmute": "Hiqni heshtim mikrofoni",
"videoblur": "Aktivizo/Çaktivizo turbullim videoje",
"videomute": "Nis / Ndal kamerën",
"whiteboard": "Shfaq / Fshih tabelën"
"videomute": "Ndal kamerën",
"videomuteGUMPending": "Po lidhet kamera juaj",
"videounmute": "Nis kamerën"
},
"addPeople": "Shtoni persona te thirrja juaj",
"audioOnlyOff": "Çaktivizo mënyrën “Sasi e ulët të dhënash trafiku”",
@@ -1161,6 +1273,7 @@
"chat": "Hap / Mbyll fjalosje",
"clap": "Duartrokitje",
"closeChat": "Mbyll fjalosjen",
"closeParticipantsPane": "Mbylle kuadratin e pjesëmarrësve",
"closeReactionsMenu": "Mbyll menu reagimesh",
"disableNoiseSuppression": "Çaktivizo mbytje zhurmash",
"disableReactionSounds": "Mund të çaktivizoni tinguj reagimesh për këtë takim",
@@ -1169,6 +1282,7 @@
"download": "Shkarkoni aplikacione tonat",
"e2ee": "Fshehtëzim Skaj-Më-Skaj",
"embedMeeting": "Trupëzoni takim",
"enableNoiseSuppression": "Aktivizoni mbytje zhurmash",
"endConference": "Përfundoje takimin për të tërë",
"enterFullScreen": "Shiheni sa krejt ekrani",
"enterTileView": "Kalo te pamja me kuadrate",
@@ -1193,9 +1307,10 @@
"lowerYourHand": "Ulni dorën",
"moreActions": "Më tepër veprime",
"moreOptions": "Më tepër veprime",
"mute": "Heshtoje / Hiqi heshtimin",
"mute": "Heshto mikrofonin",
"muteEveryone": "Heshto gjithkënd",
"muteEveryonesVideo": "Çaktivizo videon e gjithkujt",
"muteGUMPending": "Po lidhet mikrofoni juaj",
"noAudioSignalDesc": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e ndërrimit të pajisjes.",
"noAudioSignalDescSuggestion": "Nëse s’e keni heshtuar që nga rregullimet e sistemit, ose nga hardware-i, shihni mundësinë e kalimit te pajisja e sugjeruar.",
"noAudioSignalDialInDesc": "Mund t’i bini numrit edhe duke përdorur:",
@@ -1210,7 +1325,7 @@
"pip": "Kalo nën mënyrën “Picture-in-Picture”",
"privateMessage": "Dërgo mesazh privat",
"profile": "Përpunoni profilin tuaj",
"raiseHand": "Ngrini / Ulni dorën",
"raiseHand": "Ngrini dorën",
"raiseYourHand": "Ngrini dorën",
"reactionBoo": "Dërgoni reagim me ya",
"reactionClap": "Dërgoni reagim me duartrokitje",
@@ -1218,6 +1333,7 @@
"reactionLike": "Dërgoni reagim me “thumbs up”",
"reactionSilence": "Dërgoni reagim me heshtje",
"reactionSurprised": "Dërgoni reagim të befasuari",
"reactions": "Reagime",
"security": "Mundësi sigurie",
"selectBackground": "Përzgjidhni sfond",
"shareRoom": "Ftoni dikë",
@@ -1237,17 +1353,17 @@
"talkWhileMutedPopup": "Po provoni të flisni? Jeni i heshtuar.",
"tileViewToggle": "Kalo në/Dil nga mënyra mozaik",
"toggleCamera": "Hapni/Mbyllni kamerën",
"videoSetting": "Rregullime videoje",
"videomute": "Nisni / Ndalni kamerën"
"unmute": "Hiqni heshtimin e mikrofonit",
"videoSettings": "Rregullime videoje",
"videomute": "Ndalni kamerën",
"videomuteGUMPending": "Po lidhet kamera juaj",
"videounmute": "Nisni kamerën"
},
"transcribing": {
"ccButtonTooltip": "Ndali / Nisi titrat",
"error": "Transkriptimi dështoi. Ju lutemi, riprovoni.",
"expandedLabel": "Transkriptimi aktualisht është aktiv",
"failedToStart": "S’u arrit të nisej transkriptim",
"labelToolTip": "Takimit po i bëhet transkriptim",
"off": "Transkriptimi u ndal",
"pending": "Po përgatitet të transkriptohet takimi…",
"sourceLanguageDesc": "Aktualisht si gjuhë takimi është caktuar <b>{{sourceLanguage}}</b>. <br/> Mund ta ndryshoni që nga ",
"sourceLanguageHere": "këtu",
"start": "Fillo shfaqje titrash",
@@ -1284,7 +1400,7 @@
"audioOnly": "AUD",
"audioOnlyExpanded": "Gjendeni nën mënyrën “gjerësi e ulët bande”. Nën këtë mënyrë, do të merrni vetëm audio dhe tregim ekrani.",
"bestPerformance": "Punimi më i mirë",
"callQuality": "Cilësi videoje",
"callQuality": "Cilësi Videoje (0 për punimin më të mirë, 3 për cilësinë më të lartë)",
"hd": "CL",
"hdTooltip": "Po shihet video në cilësi të lartë",
"highDefinition": "Cilësi e Lartë",
@@ -1326,6 +1442,10 @@
"videomute": "Pjesëmarrësi ka ndalur kamerën"
},
"virtualBackground": {
"accessibilityLabel": {
"currentBackground": "Sfondi i tanishëm: {{background}}",
"selectBackground": "Përzgjidhni një sfond"
},
"addBackground": "Shtoni sfond",
"apply": "Zbatoje",
"backgroundEffectError": "S’u arrit të zbatohej efekt sfondi.",
@@ -1343,13 +1463,20 @@
"none": "Asnjë",
"pleaseWait": "Ju lutemi, pritni…",
"removeBackground": "Hiqe sfondin",
"slightBlur": "Turbullim Paksa",
"slightBlur": "Gjysmë-Turbullim",
"title": "Sfonde virtualë",
"uploadedImage": "Ngarkoi figurën {{index}}",
"webAssemblyWarning": "Nuk mbulohet WebAssembly",
"webAssemblyWarningDescription": "WebAssembly e çaktivizuar ose e pambuluar nga ky shfletues"
},
"visitorsLabel": "Numër vizitorësh: {{count}}",
"visitors": {
"chatIndicator": "(vizitor)",
"labelTooltip": "Numër vizitorësh: {{count}}",
"notification": {
"description": "Që të merrni pjesë, ngrini dorën",
"title": "Jeni vizitor në takim"
}
},
"volumeSlider": "Rrëshqitës volumi",
"welcomepage": {
"accessibilityLabel": {
@@ -1382,6 +1509,7 @@
"microsoftLogo": "Stemë Microsoft-i",
"policyLogo": "Stemë Rregullash"
},
"meetingsAccessibilityLabel": "Takime",
"mobileDownLoadLinkAndroid": "Shkarkoni aplikacionin për celular me Android",
"mobileDownLoadLinkFDroid": "Shkarkoni aplikacionin për celular me F-Droid",
"mobileDownLoadLinkIos": "Shkarkoni aplikacionin për celular me iOS",
@@ -1405,6 +1533,7 @@
"whiteboard": {
"accessibilityLabel": {
"heading": "Tabelë shënimesh"
}
},
"screenTitle": "Tabelë shënimesh"
}
}

View File

@@ -939,7 +939,7 @@
"or": "or",
"premeeting": "Pre meeting",
"proceedAnyway": "Proceed anyway",
"recordingWarning": "This call might be recorded",
"recordingWarning": "Other participants may be recording this call",
"screenSharingError": "Screen sharing error:",
"showScreen": "Enable pre meeting screen",
"startWithPhone": "Start with phone audio",

10
package-lock.json generated
View File

@@ -61,7 +61,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/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -12943,8 +12943,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
"integrity": "sha512-i1ygVANxENUVAuWpo/KPcgk1LW0w6/LVnib9+xfcvZs7f1o/58eolG0k/rCJEUEpUWivHgdY7tKTbhHj8yPVDQ==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
"integrity": "sha512-qs18nB89iQgiNR/UWTg/GCuEqUAIBdJRyFv9r0EaDThkQS8vHLNzYO9UQId8xZxiOGOR2kunJO84SbcivOTqbw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -29527,8 +29527,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
"integrity": "sha512-i1ygVANxENUVAuWpo/KPcgk1LW0w6/LVnib9+xfcvZs7f1o/58eolG0k/rCJEUEpUWivHgdY7tKTbhHj8yPVDQ==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
"integrity": "sha512-qs18nB89iQgiNR/UWTg/GCuEqUAIBdJRyFv9r0EaDThkQS8vHLNzYO9UQId8xZxiOGOR2kunJO84SbcivOTqbw==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -67,7 +67,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/v1773.0.0+cc9b0e60/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1777.0.0+3898d7aa/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -206,9 +206,9 @@ const PreMeetingScreen = ({
{children}
{_buttons.length && <Toolbox toolbarButtons = { _buttons } />}
{skipPrejoinButton}
{showRecordingWarning && <RecordingWarning />}
{showUnsafeRoomWarning && <UnsafeRoomWarning />}
{showDeviceStatus && <DeviceStatus />}
{showRecordingWarning && <RecordingWarning />}
</div>
</div>
</div>

View File

@@ -2,53 +2,37 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { makeStyles } from 'tss-react/mui';
import Icon from '../../../icons/components/Icon';
import { IconRecord } from '../../../icons/svg';
import { withPixelLineHeight } from '../../../styles/functions.web';
const useStyles = makeStyles()(theme => {
return {
container: {
backgroundColor: theme.palette.warning01,
borderRadius: theme.shape.borderRadius,
color: theme.palette.text04,
warning: {
bottom: 0,
color: theme.palette.text03,
display: 'flex',
justifyContent: 'center',
...withPixelLineHeight(theme.typography.bodyShortRegular),
marginBottom: theme.spacing(3),
marginTop: theme.spacing(2),
paddingBottom: theme.spacing(2),
paddingTop: theme.spacing(2),
width: '100%'
},
warning: {
alignItems: 'center',
display: 'flex',
paddingLeft: theme.spacing(3),
paddingRight: theme.spacing(3)
},
text: {
fontWeight: 600,
paddingLeft: theme.spacing(2)
paddingRight: theme.spacing(3),
position: 'absolute',
width: '100%',
'@media (max-width: 720px)': {
position: 'relative'
}
}
};
});
const RecordingWarning = () => {
const { t } = useTranslation();
const { classes, theme } = useStyles();
const color = theme.palette.icon04;
const { classes } = useStyles();
return (
<div className = { classes.container }>
<div className = { classes.warning }>
<Icon
color = { color }
src = { IconRecord } />
<span className = { classes.text }>
{t('prejoin.recordingWarning')}
</span>
</div>
<div className = { classes.warning }>
{t('prejoin.recordingWarning')}
</div>
);
};

View File

@@ -33,12 +33,12 @@ export function toggleScreensharing(enabled: boolean, _ignore1?: boolean, _ignor
if (enabled) {
const isSharing = isLocalVideoTrackDesktop(state);
if (!isSharing) {
_startScreenSharing(dispatch, state);
if (isSharing) {
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.abort();
} else {
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.launch();
_startScreenSharing(dispatch, state);
}
Platform.OS === 'android' && JitsiMeetMediaProjectionModule.abort();
} else {
dispatch(setScreenshareMuted(true));
dispatch(setVideoMuted(false, VIDEO_MUTISM_AUTHORITY.SCREEN_SHARE));

View File

@@ -1,11 +1,13 @@
import React, { useCallback } from 'react';
import { TouchableOpacity } from 'react-native';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { openHighlightDialog } from '../../../recording/actions.native';
import HighlightButton from '../../../recording/components/Recording/native/HighlightButton';
import RecordingLabel from '../../../recording/components/native/RecordingLabel';
import { getActiveSession } from '../../../recording/functions';
import VisitorsCountLabel from '../../../visitors/components/native/VisitorsCountLabel';
import RaisedHandsCountLabel from './RaisedHandsCountLabel';
@@ -28,7 +30,10 @@ interface IProps {
const AlwaysOnLabels = ({ createOnPress }: IProps) => {
const dispatch = useDispatch();
const openHighlightDialogCallback = useCallback(() => dispatch(openHighlightDialog()), [ dispatch ]);
const isStreaming = useSelector((state: IReduxState) =>
Boolean(getActiveSession(state, JitsiRecordingConstants.mode.STREAM)));
const openHighlightDialogCallback = useCallback(() =>
dispatch(openHighlightDialog()), [ dispatch ]);
return (<>
<TouchableOpacity
@@ -36,11 +41,14 @@ const AlwaysOnLabels = ({ createOnPress }: IProps) => {
onPress = { createOnPress(LABEL_ID_RECORDING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.FILE } />
</TouchableOpacity>
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_STREAMING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
</TouchableOpacity>
{
isStreaming
&& <TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { createOnPress(LABEL_ID_STREAMING) } >
<RecordingLabel mode = { JitsiRecordingConstants.mode.STREAM } />
</TouchableOpacity>
}
<TouchableOpacity
hitSlop = { LabelHitSlop }
onPress = { openHighlightDialogCallback }>

View File

@@ -43,7 +43,11 @@ import {
import { INotificationProps } from '../notifications/types';
import { open as openParticipantsPane } from '../participants-pane/actions';
import { getParticipantsPaneOpen } from '../participants-pane/functions';
import { isPrejoinPageVisible, shouldAutoKnock } from '../prejoin/functions';
import {
isPrejoinEnabledInConfig,
isPrejoinPageVisible,
shouldAutoKnock
} from '../prejoin/functions';
import {
KNOCKING_PARTICIPANT_ARRIVED_OR_UPDATED,
@@ -269,7 +273,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
const { membersOnly } = state['features/base/conference'];
const nonFirstFailure = Boolean(membersOnly);
const { isDisplayNameRequiredError } = state['features/lobby'];
const { prejoinConfig } = state['features/base/config'];
if (error.name === JitsiConferenceErrors.MEMBERS_ONLY_ERROR) {
if (typeof error.recoverable === 'undefined') {
@@ -284,7 +287,9 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
dispatch(openLobbyScreen());
// if there was an error about display name and pre-join is not enabled
if (shouldAutoKnock(state) || (isDisplayNameRequiredError && !prejoinConfig?.enabled) || lobbyWaitingForHost) {
if (shouldAutoKnock(state)
|| (isDisplayNameRequiredError && !isPrejoinEnabledInConfig(state))
|| lobbyWaitingForHost) {
dispatch(startKnocking());
}

View File

@@ -11,6 +11,7 @@ import { getFeatureFlag } from '../../base/flags/functions';
import { IconCloseLarge } from '../../base/icons/svg';
import { toState } from '../../base/redux/functions';
import { cancelKnocking } from '../../lobby/actions.native';
import { isPrejoinEnabledInConfig } from '../../prejoin/functions';
import HeaderNavigationButton from './components/HeaderNavigationButton';
@@ -50,10 +51,8 @@ export function screenHeaderCloseButton(goBack: (e?: GestureResponderEvent | Rea
*/
export function isPrejoinPageEnabled(stateful: IStateful) {
const state = toState(stateful);
const { prejoinConfig } = state['features/base/config'];
const isPrejoinEnabledInConfig = prejoinConfig?.enabled;
return getFeatureFlag(state, PREJOIN_PAGE_ENABLED, isPrejoinEnabledInConfig ?? true);
return getFeatureFlag(state, PREJOIN_PAGE_ENABLED, isPrejoinEnabledInConfig(state));
}
/**

View File

@@ -72,7 +72,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
}
case NON_PARTICIPANT_MESSAGE_RECEIVED: {
const { id, data } = action;
const { id, json: data } = action;
const isNewPoll = data.type === COMMAND_NEW_POLL;
_handleReceivePollsMessage({

View File

@@ -42,7 +42,6 @@ import { isDisplayNameRequired, isRoomNameEnabled } from '../../functions';
import { IPrejoinProps } from '../../types';
import { hasDisplayName } from '../../utils';
import RecordingWarning from './RecordingWarning';
import { preJoinStyles as styles } from './styles';
@@ -60,8 +59,6 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
const isDisplayNameReadonly = useSelector(isNameReadOnly);
const roomName = useSelector((state: IReduxState) => getConferenceName(state));
const roomNameEnabled = useSelector((state: IReduxState) => isRoomNameEnabled(state));
const showRecordingWarning
= useSelector((state: IReduxState) => state['features/base/config'].recordings?.showPrejoinWarning);
const participantName = localParticipant?.name;
const [ displayName, setDisplayName ]
= useState(participantName || '');
@@ -182,7 +179,6 @@ const Prejoin: React.FC<IPrejoinProps> = ({ navigation }: IPrejoinProps) => {
</Text>
</View>
)}
{showRecordingWarning && <RecordingWarning />}
</View>
<LargeVideo />
</View>

View File

@@ -8,30 +8,19 @@ import {
ViewStyle
} from 'react-native';
import Icon from '../../../base/icons/components/Icon';
import { IconRecord } from '../../../base/icons/svg';
import BaseTheme from '../../../base/ui/components/BaseTheme.native';
import { preJoinStyles as styles } from './styles';
const RecordingWarning = (): JSX.Element => {
const { t } = useTranslation();
const color = BaseTheme.palette.icon04;
return (
<View style = { styles.recordingWarningContainer as StyleProp<ViewStyle> }>
<View style = { styles.recordingWarning as StyleProp<ViewStyle> }>
<Icon
color = { color }
size = { 20 }
src = { IconRecord } />
<Text
numberOfLines = { 1 }
style = { styles.recordingWarningText as StyleProp<TextStyle> }>
{ t('prejoin.recordingWarning') }
</Text>
</View>
<View style = { styles.recordingWarning as StyleProp<ViewStyle> }>
<Text
numberOfLines = { 1 }
style = { styles.recordingWarningText as StyleProp<TextStyle> }>
{ t('prejoin.recordingWarning') }
</Text>
</View>
);
};

View File

@@ -122,30 +122,16 @@ export const preJoinStyles = {
paddingHorizontal: BaseTheme.spacing[3],
paddingVertical: BaseTheme.spacing[1]
},
recordingWarningContainer: {
backgroundColor: BaseTheme.palette.warning01,
borderRadius: BaseTheme.shape.borderRadius,
color: BaseTheme.palette.text04,
recordingWarning: {
display: 'flex',
justifyContent: 'center',
lineHeight: 22,
marginBottom: BaseTheme.spacing[2],
marginTop: BaseTheme.spacing[1],
paddingBottom: BaseTheme.spacing[1],
paddingTop: BaseTheme.spacing[1],
width: 'auto'
},
recordingWarning: {
alignItems: 'center',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
paddingLeft: BaseTheme.spacing[2],
paddingRight: BaseTheme.spacing[2]
},
recordingWarningText: {
fontWeight: '600',
paddingLeft: BaseTheme.spacing[1]
color: BaseTheme.palette.text03
},
unsafeRoomWarningContainer: {
height: '100%',

View File

@@ -42,6 +42,16 @@ export function isDisplayNameRequired(state: IReduxState): boolean {
|| state['features/base/config']?.requireDisplayName);
}
/**
* Selector for determining if the prejoin page is enabled in config. Defaults to `true`.
*
* @param {IReduxState} state - The state of the app.
* @returns {boolean}
*/
export function isPrejoinEnabledInConfig(state: IReduxState): boolean {
return state['features/base/config'].prejoinConfig?.enabled ?? true;
}
/**
* Selector for determining if the prejoin display name field is visible.
*
@@ -154,7 +164,7 @@ export function isJoinByPhoneDialogVisible(state: IReduxState): boolean {
*/
export function isPrejoinPageVisible(state: IReduxState): boolean {
return Boolean(navigator.product !== 'ReactNative'
&& state['features/base/config'].prejoinConfig?.enabled
&& isPrejoinEnabledInConfig(state)
&& state['features/prejoin']?.showPrejoin
&& !(state['features/base/config'].enableForcedReload && state['features/prejoin'].skipPrejoinOnReload));
}
@@ -166,12 +176,11 @@ export function isPrejoinPageVisible(state: IReduxState): boolean {
* @returns {boolean}
*/
export function shouldAutoKnock(state: IReduxState): boolean {
const { iAmRecorder, iAmSipGateway, prejoinConfig } = state['features/base/config'];
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
const { userSelectedSkipPrejoin } = state['features/base/settings'];
const { autoKnock } = getLobbyConfig(state);
const isPrejoinEnabled = prejoinConfig?.enabled;
return Boolean(((isPrejoinEnabled && !userSelectedSkipPrejoin)
return Boolean(((isPrejoinEnabledInConfig(state) && !userSelectedSkipPrejoin)
|| autoKnock || (iAmRecorder && iAmSipGateway))
&& !state['features/lobby'].knocking);
}

View File

@@ -404,7 +404,7 @@ export function stopLocalVideoRecording() {
*/
export function showStartRecordingNotificationWithCallback(openRecordingDialog: Function) {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const state = getState();
let state = getState();
const { recordings } = state['features/base/config'];
const { suggestRecording } = recordings || {};
const recordButtonProps = getRecordButtonProps(state);
@@ -427,9 +427,9 @@ export function showStartRecordingNotificationWithCallback(openRecordingDialog:
customActionType: [ BUTTON_TYPES.PRIMARY ],
customActionNameKey: [ 'notify.suggestRecordingAction' ],
customActionHandler: [ () => {
state = getState();
const isModerator = isLocalParticipantModerator(state);
const { recordingService } = state['features/base/config'];
const canBypassDialog = isModerator
&& recordingService?.enabled
&& isJwtFeatureEnabled(state, 'recording', true);

View File

@@ -397,7 +397,8 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
});
}
if (this.state.shouldRecordTranscription) {
if (this.state.selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE
&& this.state.shouldRecordTranscription) {
dispatch(setRequestingSubtitles(true, false, null));
}

View File

@@ -36,9 +36,6 @@ interface ILocalRecordingManager {
const getMimeType = (): string => {
const possibleTypes = [
'video/mp4;codecs=h264',
'video/webm;codecs=h264',
'video/webm;codecs=vp9',
'video/webm;codecs=vp8'
];
@@ -164,7 +161,11 @@ const LocalRecordingManager: ILocalRecordingManager = {
this.audioContext = undefined;
this.audioDestination = undefined;
this.totalSize = MAX_SIZE;
setTimeout(() => this.saveRecording(this.recordingData, this.getFilename()), 1000);
setTimeout(() => {
if (this.recordingData.length > 0) {
this.saveRecording(this.recordingData, this.getFilename());
}
}, 1000);
}
},

View File

@@ -51,7 +51,9 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
* @returns {React$Component}
*/
_renderAdvancedOptions() {
if (!this._canStartTranscribing()) {
const { selectedRecordingService } = this.props;
if (selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE || !this._canStartTranscribing()) {
return null;
}

View File

@@ -59,7 +59,9 @@ class StartRecordingDialogContent extends AbstractStartRecordingDialogContent {
* @returns {React$Component}
*/
_renderAdvancedOptions() {
if (!this._canStartTranscribing()) {
const { selectedRecordingService } = this.props;
if (selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE || !this._canStartTranscribing()) {
return null;
}

View File

@@ -43,7 +43,7 @@ class RecordingExpandedLabel extends ExpandedLabel<IProps> {
*/
_getLabel() {
const { _status, mode, t } = this.props;
let postfix = 'recording', prefix = 'expandedOn'; // Default values.
let postfix = 'expandedOn', prefix = 'recording'; // Default values.
switch (mode) {
case JitsiRecordingConstants.mode.STREAM:

View File

@@ -11,6 +11,7 @@ import { updateSettings } from '../../../base/settings/actions';
import Switch from '../../../base/ui/components/native/Switch';
import { navigate } from '../../../mobile/navigation/components/settings/SettingsNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { isPrejoinEnabledInConfig } from '../../../prejoin/functions';
import FormRow from './FormRow';
import FormSection from './FormSection';
@@ -27,8 +28,7 @@ const GeneralSection = () => {
const showPrejoinPage = !userSelectedSkipPrejoin;
let showPrejoinSettings = useSelector(
(state: IReduxState) => state['features/base/config'].prejoinConfig?.enabled);
let showPrejoinSettings = useSelector(isPrejoinEnabledInConfig);
const { language = DEFAULT_LANGUAGE } = i18next;

View File

@@ -10,6 +10,7 @@ import { getHideSelfView } from '../base/settings/functions.any';
import { parseStandardURIString } from '../base/util/uri';
import { isStageFilmstripEnabled } from '../filmstrip/functions';
import { isFollowMeActive } from '../follow-me/functions';
import { isPrejoinEnabledInConfig } from '../prejoin/functions';
import { isReactionsEnabled } from '../reactions/functions.any';
import { iAmVisitor } from '../visitors/functions';
@@ -114,7 +115,7 @@ export function getMoreTabProps(stateful: IStateful) {
maxStageParticipants: state['features/base/settings'].maxStageParticipants,
showLanguageSettings: configuredTabs.includes('language'),
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
showPrejoinSettings: state['features/base/config'].prejoinConfig?.enabled,
showPrejoinSettings: isPrejoinEnabledInConfig(state),
stageFilmstripEnabled
};
}

View File

@@ -6,6 +6,7 @@ local saslprep = require "util.encodings".stringprep.saslprep;
local secure_equals = require "util.hashes".equals;
local shared_secret = module:get_option_string('shared_secret');
local shared_secret_prev = module:get_option_string('shared_secret_prev');
if shared_secret == nil then
module:log('error', 'No shared_secret specified. No secret to operate on!');
return;
@@ -24,6 +25,9 @@ function provider.test_password(username, password)
if secure_equals(password, saslprep(shared_secret)) then
return true;
elseif (shared_secret_prev ~= nil and secure_equals(password, saslprep(shared_secret_prev))) then
module:log("info", "Accepting login using previous shared secret, username=%s", username);
return true;
else
return nil, "Auth failed. Invalid username or password.";
end

View File

@@ -15,6 +15,12 @@ end
module:depends("jitsi_session");
local measure_pre_fetch_fail = module:measure('pre_fetch_fail', 'counter');
local measure_verify_fail = module:measure('verify_fail', 'counter');
local measure_success = module:measure('success', 'counter');
local measure_ban = module:measure('ban', 'counter');
local measure_post_auth_fail = module:measure('post_auth_fail', 'counter');
-- define auth provider
local provider = {};
@@ -74,6 +80,7 @@ function provider.get_sasl_handler(session)
module:log("warn",
"Error verifying token on pre authentication stage:%s, reason:%s", pre_event_result.error, pre_event_result.reason);
session.auth_token = nil;
measure_pre_fetch_fail(1);
return pre_event_result.res, pre_event_result.error, pre_event_result.reason;
end
@@ -82,21 +89,21 @@ function provider.get_sasl_handler(session)
module:log("warn",
"Error verifying token err:%s, reason:%s", error, reason);
session.auth_token = nil;
measure_verify_fail(1);
return res, error, reason;
end
local shouldAllow = prosody.events.fire_event("jitsi-access-ban-check", session);
if shouldAllow == false then
module:log("warn", "user is banned")
measure_ban(1);
return false, "not-allowed", "user is banned";
end
local customUsername
= prosody.events.fire_event("pre-jitsi-authentication", session);
if (customUsername) then
local customUsername = prosody.events.fire_event("pre-jitsi-authentication", session);
if customUsername then
self.username = customUsername;
elseif (session.previd ~= nil) then
elseif session.previd ~= nil then
for _, session1 in pairs(sessions) do
if (session1.resumption_token == session.previd) then
self.username = session1.username;
@@ -112,9 +119,11 @@ function provider.get_sasl_handler(session)
module:log("warn",
"Error verifying token on post authentication stage :%s, reason:%s", post_event_result.error, post_event_result.reason);
session.auth_token = nil;
measure_post_auth_fail(1);
return post_event_result.res, post_event_result.error, post_event_result.reason;
end
measure_success(1);
return res;
end

View File

@@ -1,8 +1,5 @@
local st = require "util.stanza";
local is_feature_allowed = module:require "util".is_feature_allowed;
local token_util = module:require "token/util".new(module);
local accepted_rayo_iq_token_issuers = module:get_option_array("accepted_rayo_iq_token_issuers");
-- filters jibri iq in case of requested from jwt authenticated session that
-- has features in the user context, but without feature for recording
@@ -15,27 +12,6 @@ module:hook("pre-iq/full", function(event)
local token = session.auth_token;
if jibri.attr.action == 'start' then
local errorReason;
if accepted_rayo_iq_token_issuers then
local iq_token = jibri.attr.token;
if iq_token then
local session = {};
session.auth_token = iq_token;
local verified, reason = token_util:process_and_verify_token(
session, accepted_rayo_iq_token_issuers);
if verified then
return nil; -- this will proceed with dispatching the stanza
end
errorReason = reason;
else
errorReason = 'No recording token provided';
end
module:log("warn", "not a valid token %s", tostring(errorReason));
session.send(st.error_reply(stanza, "auth", "forbidden"));
return true;
end
if token == nil
or not is_feature_allowed(session.jitsi_meet_context_features,
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming')

View File

@@ -12,6 +12,8 @@ local jid_bare = require "util.jid".bare;
local sessions = prosody.full_sessions;
local measure_drop = module:measure('drop', 'counter');
local main_muc_component_host = module:get_option_string('main_muc');
if main_muc_component_host == nil then
module:log('error', 'main_muc not configured. Cannot proceed.');
@@ -108,6 +110,7 @@ module:hook("pre-iq/full", function(event)
then
module:log("warn",
"Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
measure_drop(1);
session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
return true;
end

View File

@@ -27,6 +27,9 @@ module:depends("jitsi_session");
local jid_split = require 'util.jid'.split;
local jid_bare = require 'util.jid'.bare;
local jid_prep = require "util.jid".prep;
local jid_resource = require "util.jid".resource;
local resourceprep = require "util.encodings".stringprep.resourceprep;
local json = require 'util.json';
local filters = require 'util.filters';
local st = require 'util.stanza';
@@ -235,6 +238,77 @@ 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
origin.send(st.error_reply(stanza, "cancel", "bad-request"));
return true;
end
if item.attr.jid then -- Validate provided JID
item.attr.jid = jid_prep(item.attr.jid);
if not item.attr.jid then
origin.send(st.error_reply(stanza, "modify", "jid-malformed"));
return true;
elseif jid_resource(item.attr.jid) then
origin.send(st.error_reply(stanza, "modify", "jid-malformed", "Bare JID expected, got full JID"));
return true;
end
end
if item.attr.nick then -- Validate provided nick
item.attr.nick = resourceprep(item.attr.nick);
if not item.attr.nick then
origin.send(st.error_reply(stanza, "modify", "jid-malformed", "invalid nickname"));
return true;
end
end
if not item.attr.jid and item.attr.nick then
-- COMPAT Workaround for Miranda sending 'nick' instead of 'jid' when changing affiliation
local occupant = self:get_occupant_by_nick(self.jid.."/"..item.attr.nick);
if occupant then item.attr.jid = occupant.bare_jid; end
elseif item.attr.role and not item.attr.nick and item.attr.jid then
-- Role changes should use nick, but we have a JID so pull the nick from that
local nick = self:get_occupant_jid(item.attr.jid);
if nick then item.attr.nick = jid_resource(nick); end
end
local actor = stanza.attr.from;
local reason = item:get_child_text("reason");
local success, errtype, err
if item.attr.affiliation and item.attr.jid and not item.attr.role then
local registration_data;
if item.attr.nick then
local room_nick = self.jid.."/"..item.attr.nick;
local existing_occupant = self:get_occupant_by_nick(room_nick);
if existing_occupant and existing_occupant.bare_jid ~= item.attr.jid then
module:log("debug", "Existing occupant for %s: %s does not match %s", room_nick, existing_occupant.bare_jid, item.attr.jid);
self:set_role(true, room_nick, nil, "This nickname is reserved");
end
module:log("debug", "Reserving %s for %s (%s)", item.attr.nick, item.attr.jid, item.attr.affiliation);
registration_data = { reserved_nickname = item.attr.nick };
end
success, errtype, err = self:set_affiliation(actor, item.attr.jid, item.attr.affiliation, reason, registration_data);
elseif item.attr.role and item.attr.nick and not item.attr.affiliation then
success, errtype, err = self:set_role(actor, self.jid.."/"..item.attr.nick, item.attr.role, reason);
else
success, errtype, err = nil, "cancel", "bad-request";
end
self:save(true);
if not success then
origin.send(st.error_reply(stanza, errtype, err));
else
origin.send(st.reply(stanza));
end
end
-- operates on already loaded lobby muc module
function process_lobby_muc_loaded(lobby_muc, host_module)
module:log('debug', 'Lobby muc loaded');
@@ -441,6 +515,24 @@ process_host_module(main_muc_component_config, function(host_module, host)
end
end
end);
-- listen for admin set
for event_name, method in pairs {
-- Normal room interactions
["iq-set/bare/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ;
-- Host room
["iq-set/host/http://jabber.org/protocol/muc#admin:query"] = "handle_admin_query_set_command" ;
} do
host_module:hook(event_name, function (event)
local origin, stanza = event.origin, event.stanza;
local room_jid = jid_bare(stanza.attr.to);
local room = get_room_from_jid(room_jid);
if room then
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
end);
function handle_create_lobby(event)

View File

@@ -37,16 +37,16 @@ local function load_config()
-- Max allowed message rate in events per second.
config.message_rate = module:get_option_number("rate_limit_message_rate", 3);
-- A list of jids for which sessions we ignore rate limiting
config.whitelist_jids = module:get_option_set("rate_limit_whitelist_jids", {});
-- A list of hosts for which sessions we ignore rate limiting
config.whitelist_hosts = module:get_option_set("rate_limit_whitelist_hosts", {});
local wl = "";
for ip in config.whitelist do wl = wl .. ip .. "," end
local wl_jids = "";
for j in config.whitelist_jids do wl_jids = wl_jids .. j .. "," end
local wl_hosts = "";
for j in config.whitelist_hosts do wl_hosts = wl_hosts .. j .. "," end
module:log("info", "Loaded configuration: ");
module:log("info", "- session_rate=%s bytes/sec, timeout=%s sec, cache size=%s, whitelist=%s, whitelist_jids=%s",
config.session_rate, config.timeout, config.cache_size, wl, wl_jids);
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
@@ -70,8 +70,8 @@ local function is_whitelisted(ip)
return false;
end
local function is_whitelisted_jid(jid)
return config.whitelist_jids:contains(jid);
local function is_whitelisted_host(h)
return config.whitelist_hosts:contains(h);
end
-- Discover real remote IP of a session
@@ -125,10 +125,10 @@ local function throttle_session(session)
if not session.jitsi_throttle then
if (session.conn and session.conn.setlimit) then
-- 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, session.ip);
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.", config.session_rate, session, session.ip);
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
@@ -179,7 +179,7 @@ local function filter_hook(session)
local request = get_request_from_conn(session.conn);
local ip = request and request.ip or session.ip;
module:log("debug", "New session from %s", ip);
if is_whitelisted(ip) or (session.username and is_whitelisted_jid(session.username..'@'..session.host)) then
if is_whitelisted(ip) or is_whitelisted_host(session.host) then
return;
end

View File

@@ -10,6 +10,9 @@ local jid_bare = require 'util.jid'.bare;
local DEBUG = false;
local measure_success = module:measure('success', 'counter');
local measure_fail = module:measure('fail', 'counter');
local function is_admin(jid)
return um_is_admin(jid, host);
end
@@ -36,9 +39,9 @@ if token_util == nil then
end
module:log("debug",
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
tostring(host), tostring(token_util.appId), tostring(token_util.appSecret),
tostring(token_util.allowEmptyToken));
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
tostring(host), tostring(token_util.appId), tostring(token_util.appSecret),
tostring(token_util.allowEmptyToken));
-- option to disable room modification (sending muc config form) for guest that do not provide token
local require_token_for_moderation;
@@ -91,16 +94,20 @@ module:hook("muc-room-pre-create", function(event)
local origin, stanza = event.origin, event.stanza;
if DEBUG then module:log("debug", "pre create: %s %s", tostring(origin), tostring(stanza)); end
if not verify_user(origin, stanza) then
measure_fail(1);
return true; -- Returning any value other than nil will halt processing of the event
end
measure_success(1);
end, 99);
module:hook("muc-occupant-pre-join", function(event)
local origin, room, stanza = event.origin, event.room, event.stanza;
if DEBUG then module:log("debug", "pre join: %s %s", tostring(room), tostring(stanza)); end
if not verify_user(origin, stanza) then
measure_fail(1);
return true; -- Returning any value other than nil will halt processing of the event
end
measure_success(1);
end, 99);
for event_name, method in pairs {

View File

@@ -14,6 +14,7 @@ local ends_with = main_util.ends_with;
local http_get_with_retry = main_util.http_get_with_retry;
local extract_subdomain = main_util.extract_subdomain;
local starts_with = main_util.starts_with;
local table_shallow_copy = main_util.table_shallow_copy;
local cjson_safe = require 'cjson.safe'
local timer = require "util.timer";
local async = require "util.async";
@@ -119,17 +120,26 @@ function Util.new(module)
end
if self.cacheKeysUrl then
self.cachedKeys = {};
local update_keys_cache;
update_keys_cache = async.runner(function (name)
content, code, cache_for = http_get_with_retry(self.cacheKeysUrl, nr_retries);
if content ~= nil then
self.cachedKeys = {};
local keys_to_delete = table_shallow_copy(self.cachedKeys);
-- Let's convert any certificate to public key
for k, v in pairs(cjson_safe.decode(content)) do
if starts_with(v, '-----BEGIN CERTIFICATE-----') then
self.cachedKeys[k] = ssl.loadcertificate(v):pubkey();
-- do not clean this key if it already exists
keys_to_delete[k] = nil;
end
end
-- let's schedule the clean in an hour and a half, current tokens will be valid for an hour
timer.add_task(90*60, function ()
for k, _ in pairs(keys_to_delete) do
self.cachedKeys[k] = nil;
end
end);
if cache_for then
cache_for = tonumber(cache_for);
@@ -140,8 +150,18 @@ function Util.new(module)
timer.add_task(cache_for, function ()
update_keys_cache:run("update_keys_cache");
end);
else
-- no cache header let's consider updating in 6hours
timer.add_task(6*60*60, function ()
update_keys_cache:run("update_keys_cache");
end);
end
else
module:log('warn', 'Failed to retrieve cached public keys code:%s', code);
-- failed let's retry in 30 seconds
timer.add_task(30, function ()
update_keys_cache:run("update_keys_cache");
end);
end
end);
update_keys_cache:run("update_keys_cache");
@@ -175,19 +195,25 @@ end
-- @return the public key (the content of requested resource) or nil
function Util:get_public_key(keyId)
local content = self.cache:get(keyId);
local code;
if content == nil then
-- If the key is not found in the cache.
module:log("debug", "Cache miss for key: %s", keyId);
-- module:log("debug", "Cache miss for key: %s", keyId);
local keyurl = path.join(self.asapKeyServer, hex.to(sha256(keyId))..'.pem');
module:log("debug", "Fetching public key from: %s", keyurl);
content = http_get_with_retry(keyurl, nr_retries);
-- module:log("debug", "Fetching public key from: %s", keyurl);
content, code = http_get_with_retry(keyurl, nr_retries);
if content ~= nil then
self.cache:set(keyId, content);
else
if code == nil then
-- this is timout after nr_retries retries
module:log('warn', 'Timeout retrieving %s from %s', keyId, keyurl);
end
end
return content;
else
-- If the key is in the cache, use it.
module:log("debug", "Cache hit for key: %s", keyId);
-- module:log("debug", "Cache hit for key: %s", keyId);
return content;
end
end

View File

@@ -509,7 +509,13 @@ function process_host_module(name, callback)
end
end
function table_shallow_copy(t)
local t2 = {}
for k, v in pairs(t) do
t2[k] = v
end
return t2
end
return {
OUTBOUND_SIP_JIBRI_PREFIX = OUTBOUND_SIP_JIBRI_PREFIX;
@@ -534,4 +540,5 @@ return {
http_get_with_retry = http_get_with_retry;
ends_with = ends_with;
starts_with = starts_with;
table_shallow_copy = table_shallow_copy;
};