Compare commits

...

56 Commits

Author SHA1 Message Date
damencho
6b5e9c0b50 fix(avatar): Drops avatar url and respects url in the token. 2024-08-24 11:41:30 -05:00
damencho
ec22c1fdda fix(participants): Handles kicker undefined on participantKicked. 2024-08-23 12:19:32 -05:00
Hristo Terezov
3441954f8b fix(subtitles): ITranscriptMessage type. 2024-08-21 15:19:44 -05:00
Calin-Teodor
b0a87041da feat(rnsdk): moved deps that dont require linking 2024-08-21 17:37:41 +03:00
damencho
42586be533 fix(visitors): Visitors raise hand to be promoted, skip notification for speak line. 2024-08-21 09:27:02 -05:00
damencho
65e94bd173 fix(visitors): Do not show reactions icon in visitors join dialog. 2024-08-21 09:27:02 -05:00
Saúl Ibarra Corretgé
73c836fafb fix(android) fix joining meetings in quick succession
If the readyToClose event was fired there is no need to "leave" the
meeting, it just circles back to the app unnecessarily, potentially
creating another readyToClose event.
2024-08-21 14:06:40 +03:00
Saúl Ibarra Corretgé
d6fa066e4d fix(android) fix default value for pip.enabled
Fixes: https://github.com/jitsi/jitsi-meet/issues/15014
2024-08-21 12:47:26 +03:00
Saúl Ibarra Corretgé
22bbf4939e fix(android) remove unused method 2024-08-21 10:54:00 +02:00
Saúl Ibarra Corretgé
ca195fd708 fix(rn) improve rnsdk version script
Make sure alll dependencies are updated when we run it.
2024-08-21 10:45:07 +02:00
AHMAD KADRI
c3c0166731 Accessibility: make status notifications accessible (#15004)
* Accessibility: add aria-live to the notification component
2024-08-21 11:03:38 +03:00
Saúl Ibarra Corretgé
fc94854b72 chore(deps) npm audit fix 2024-08-19 15:03:52 +02:00
Saúl Ibarra Corretgé
c55eb68cf2 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1847.0.0+03eef6de...v1852.0.0+526ec25d
2024-08-19 13:32:10 +02:00
Saúl Ibarra Corretgé
80e08a112a chore(deps) remove remaining lodash dependencies
We are fully using lodash-es now.
2024-08-19 11:04:14 +02:00
Edgars Voroboks
896fc29af7 fix(lang): Update Latvian language translation 2024-08-17 20:58:30 -05:00
Hristo Terezov
c88ffab370 fix(auth): Preserve iceServers URL params
During authentication cycle the iceServers URL params are not passed through the various redirects. The result is that the when we finally return back to the conference page authenticated and with the JWT token the iceServers URL param is lost.
2024-08-15 22:33:24 -05:00
Saúl Ibarra Corretgé
445515da93 fix(lastN) simplify lastN calculation when in background (#15018)
There is no point in ever setting lastN to anything other than 0 when in
the background because we are not rendering anything.

When in PiP mode, the app state is not background, which might have
deceived us into adding that check there way back when.
2024-08-15 14:25:21 -04:00
Jannis
f5dbd6780b fix(lang): update german translation (#15011)
Co-authored-by: Christoph Settgast <csett86_git@quicksands.de>
2024-08-15 12:23:14 +02:00
Saúl Ibarra Corretgé
ba06121464 fix(rn) fix audio loss when switching to the bridge
The issue was fixed in RN WebRTC.
2024-08-14 16:30:17 +02:00
Calin-Teodor
7115919206 feat(android/sdk): fix jitsiview join call 2024-08-14 14:30:09 +03:00
Hristo Terezov
1d95c30893 feat(shared-video): Allow only whitelisted URLs. 2024-08-13 12:43:45 -05:00
Saúl Ibarra Corretgé
a5cd5e2733 fix(android) don't re-launch activity after closing PiP
Just remain in the background. The ongoing notification can be used to
get back to the meeting.

NOTE: This behavior only affected the app, not the SDK.
2024-08-13 17:18:26 +02:00
Saúl Ibarra Corretgé
8732675162 fix(android) fix not opening activity from notification
We need to pass the Activity to get back to in the intent, but since we
launch the notification from a Service things got a bit more involved.
2024-08-13 17:18:26 +02:00
Calin-Teodor
a074437d99 fix(android/ios/scripts): updated react native packager script path 2024-08-13 14:24:48 +03:00
José Luís Andrade
d2eb11fa5c fix(lang) update Portuguese translation 2024-08-13 12:57:22 +02:00
Saúl Ibarra Corretgé
b35200648c feat(deps) replace lodash with lodash-es
The latter supports tree-shaking and we don't need to embed the whole
500KB of lodash.
2024-08-12 10:34:44 +02:00
Calinteodor
a8958019a5 feat(chat/polls/native): added ids for tests (#14994)
* feat(chat/polls/navigation): added ids for tests and removed some unused helpers
2024-08-10 18:02:48 +03:00
Hristo Terezov
200228339b fix(gif): Restrict gif rendering to Giphy only 2024-08-09 10:01:04 -05:00
Hristo Terezov
ddc64ad687 fix(giphy): Remove proxyUrl config option. 2024-08-09 10:01:04 -05:00
Calin-Teodor
2d2bae6ec1 feat(toolbox): moved action to appropriate feature and fixed naming 2024-08-09 16:19:13 +03:00
Saúl Ibarra Corretgé
fa6dc292c1 fix(polls) improve message validation (#14991)
* fix(polls) improve message validation

- Prevent creation of too many polls
- Discard absurdly large payloads

* Update resources/prosody-plugins/mod_polls.lua

Co-authored-by: Дамян Минков <damencho@jitsi.org>

---------

Co-authored-by: Дамян Минков <damencho@jitsi.org>
2024-08-09 06:23:43 -05:00
Saúl Ibarra Corretgé
b5ac40b32a fix(build) don't run clean after compilation
It prevents the bundle analyzer from working because the stats files are
placed in the build dir.

Clean *before* building instead.
2024-08-09 12:03:03 +02:00
Mengyuan Liu
8299aa498b feat(raise-hand) group options in config.js 2024-08-09 11:39:55 +02:00
Saúl Ibarra Corretgé
ce22adfe64 fix(ios) SDK release script fixes
- Run script with tracing
- Allow empty commits (we might need a rebuild)
- Fix tag name in lite SDK
2024-08-08 07:49:26 +02:00
Jaya Allamsetty
01bcccdd99 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1846.0.0+a97a8dff...v1847.0.0+03eef6de
2024-08-07 22:21:47 -04:00
Mengyuan Liu
673a54adb1 feat(raise-han) add CTA for opening participants pane on notification 2024-08-07 17:42:49 +02:00
Calin-Teodor
f48c03e314 feat(ios): readded git tag for release sdk scritps 2024-08-07 16:09:37 +03:00
Calinteodor
7641ddad68 feat(chat/native): set limit for linkifying and replacing non unicode messages (#14979)
* feat(chat): set limit for linkifying and replacing non unicode emoji messages
2024-08-07 14:10:28 +03:00
damencho
26021b2dd0 Revert "feat: Presence identity module to support other auth mechanisms."
This reverts commit a6457db819.
2024-08-07 09:52:48 +03:00
damencho
a6457db819 feat: Presence identity module to support other auth mechanisms. 2024-08-06 19:27:52 +03:00
Patrick He
8bfa65987d feat(chat) use the original message ID for processing
This is a prerequisite for operations that rely on previous messages, such as reactions.
2024-08-06 15:30:37 +02:00
Calin-Teodor
b1c0cc5322 feat(android/ios): we no longer need to set a tag 2024-08-06 14:07:59 +03:00
Saúl Ibarra Corretgé
67cbef0d7a chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1845.0.0+515a927c...v1846.0.0+a97a8dff
2024-08-06 11:04:20 +02:00
Calinteodor
b2e06c1c94 feat(chat): revert char_limit for input (#14973)
* feat(chat): revert char_limit for message input
2024-08-06 11:12:30 +03:00
damencho
70a05e487e fix(i18n): Ignore case when normalizing languages.
Fixes #14764.
2024-08-06 11:06:52 +03:00
Calinteodor
c2a446a79b feat(toolbox/web): updated imports and fixed jsdocs (#14966)
* feat(toolbox/web): updated imports extensions and fixed some jsdocs
2024-08-06 10:51:18 +03:00
Calin-Teodor
56df23af35 dep(react-native): update for some related dependencies to latest 2024-08-06 10:50:41 +03:00
Mihaela Dumitru
1c12f5cf2a fix(visitors) remove default notification (#14970) 2024-08-05 17:55:25 +03:00
Calin-Teodor
06b727ae1a feat(chat): fixed linter 2024-08-02 14:25:41 +03:00
Calin-Teodor
a2aca30d57 feat(notifications): set numberOfLines for content text description and title 2024-08-02 14:25:41 +03:00
Calin-Teodor
68106feb57 feat(chat): set CHAR_LIMIT for chat input 2024-08-02 14:25:41 +03:00
Calin-Teodor
3066fc1d31 feat(participants): revert prev change 2024-08-02 14:17:40 +03:00
Calin-Teodor
4834fb7b6f feat(participants): check for raisedHandsQueue length before notifying participant to speak 2024-08-02 14:17:40 +03:00
Raphaël Badawi
1e101afe5a feat(invite) add email autocomplete in invite (#14610)
* feat(invite) add email value in peopleSearchQueryTypes and peopleSearchToken config

* feat(invite) pass custom auth token in search directory (e. g. for email directory)

* feat(invite) autocomplete and invitations working with custom auth token (e. g. email invite type), invite icons updated

* feat(invite) remove newly documented config from undocumented settings list

* feat(invite) jwt are now passed in the invite requests headers

* feat(invite) linter-related formatting

* feat(invite) fix default user icon regression

* feat(invite) last lint issues

* feat(invite) pass alternate token in header, not in params

* Fixes lint error

---------

Co-authored-by: Raphaël Badawi <raphael.badawi@ceo-vision.com>
Co-authored-by: Дамян Минков <damencho@jitsi.org>
2024-08-01 21:01:12 +03:00
Hristo Terezov
4d79bbb5d8 feat(window.loaded): Add new metric. (#14965) 2024-08-01 12:43:25 -04:00
Saúl Ibarra Corretgé
e31aff5afd Revert "feat(external-api) add deployment information to ready event"
This reverts commit 6727004930.
2024-07-31 17:44:56 +02:00
149 changed files with 1188 additions and 794 deletions

View File

@@ -24,9 +24,9 @@ else
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
endif
all: compile deploy clean
all: compile deploy
compile:
compile: clean
NODE_OPTIONS=--max-old-space-size=8192 \
$(WEBPACK)

View File

@@ -211,11 +211,6 @@ public class MainActivity extends JitsiMeetActivity {
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
Log.d(TAG, "Is in picture-in-picture mode: " + isInPictureInPictureMode);
if (!isInPictureInPictureMode) {
this.startActivity(new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT));
}
}
// Helper methods

View File

@@ -10,7 +10,6 @@ MVN_HTTP=0
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
DO_GIT_TAG=${GIT_TAG:-0}
if [[ $THE_MVN_REPO == http* ]]; then
MVN_HTTP=1
@@ -67,15 +66,12 @@ pushd ${THIS_DIR}/../
./gradlew publish
popd
if [[ $DO_GIT_TAG == 1 ]]; then
# The artifacts are now on the Maven repo, commit them
# The artifacts are now on the Maven repo, commit them
if [[ $MVN_HTTP == 0 ]]; then
pushd ${MVN_REPO_PATH}
git add -A .
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
popd
# Tag the release
git tag android-sdk-${SDK_VERSION}
fi
# Done!

View File

@@ -2,4 +2,4 @@
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
exec ${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command --reset-cache
exec ${THIS_DIR}/../../node_modules/react-native/scripts/packager.sh --reset-cache

View File

@@ -54,6 +54,8 @@ public class JitsiMeetActivity extends AppCompatActivity
private static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
private static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
private boolean isReadyToClose;
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -124,6 +126,8 @@ public class JitsiMeetActivity extends AppCompatActivity
@Override
public void onDestroy() {
JitsiMeetLogger.i("onDestroy()");
// Here we are trying to handle the following corner case: an application using the SDK
// is using this Activity for displaying meetings, but there is another "main" Activity
// with other content. If this Activity is "swiped out" from the recent list we will get
@@ -131,7 +135,10 @@ public class JitsiMeetActivity extends AppCompatActivity
// current meeting, but when our view is detached from React the JS <-> Native bridge won't
// be operational so the external API won't be able to notify the native side that the
// conference terminated. Thus, try our best to clean up.
leave();
if (!isReadyToClose) {
JitsiMeetLogger.i("onDestroy(): leaving...");
leave();
}
this.jitsiView = null;
@@ -149,8 +156,12 @@ public class JitsiMeetActivity extends AppCompatActivity
@Override
public void finish() {
leave();
if (!isReadyToClose) {
JitsiMeetLogger.i("finish(): leaving...");
leave();
}
JitsiMeetLogger.i("finish(): finishing...");
super.finish();
}
@@ -170,8 +181,8 @@ public class JitsiMeetActivity extends AppCompatActivity
}
public void join(JitsiMeetConferenceOptions options) {
if (this.jitsiView != null) {
this.jitsiView .join(options);
if (this.jitsiView != null) {
this.jitsiView.join(options);
} else {
JitsiMeetLogger.w("Cannot join, view is null");
}
@@ -252,6 +263,7 @@ public class JitsiMeetActivity extends AppCompatActivity
protected void onReadyToClose() {
JitsiMeetLogger.i("SDK is ready to close");
isReadyToClose = true;
finish();
}

View File

@@ -270,11 +270,6 @@ public class JitsiMeetConferenceOptions implements Parcelable {
Bundle asProps() {
Bundle props = new Bundle();
// Android always has the PiP flag set by default.
if (!featureFlags.containsKey("pip.enabled")) {
featureFlags.putBoolean("pip.enabled", true);
}
props.putBundle("flags", featureFlags);
Bundle urlProps = new Bundle();

View File

@@ -51,30 +51,34 @@ import java.util.Random;
*
* See: https://developer.android.com/guide/components/services
*/
public class JitsiMeetOngoingConferenceService extends Service
implements OngoingConferenceTracker.OngoingConferenceListener {
public class JitsiMeetOngoingConferenceService extends Service implements OngoingConferenceTracker.OngoingConferenceListener {
private static final String TAG = JitsiMeetOngoingConferenceService.class.getSimpleName();
private static final String ACTIVITY_DATA_KEY = "activityDataKey";
private static final String EXTRA_DATA_KEY = "extraDataKey";
private static final String EXTRA_DATA_BUNDLE_KEY = "extraDataBundleKey";
private static final String IS_AUDIO_MUTED_KEY = "isAudioMuted";
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver();
private boolean isAudioMuted;
private Class tapBackActivity;
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
private static void doLaunch(Context context, HashMap<String, Object> extraData) {
Activity activity = (Activity) context;
OngoingNotification.createNotificationChannel((Activity) context);
OngoingNotification.createNotificationChannel(activity);
Intent intent = new Intent(context, JitsiMeetOngoingConferenceService.class);
Bundle extraDataBundle = new Bundle();
extraDataBundle.putSerializable(EXTRA_DATA_KEY, extraData);
intent.putExtra(EXTRA_DATA_BUNDLE_KEY, extraDataBundle);
intent.putExtra(ACTIVITY_DATA_KEY, activity.getClass().getCanonicalName());
ComponentName componentName;
@@ -154,7 +158,7 @@ public class JitsiMeetOngoingConferenceService extends Service
public void onCreate() {
super.onCreate();
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this, tapBackActivity);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
@@ -190,13 +194,28 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String actionName = intent.getAction();
final Action action = Action.fromName(actionName);
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
if (action != Action.HANGUP) {
Boolean isAudioMuted = tryParseIsAudioMuted(intent);
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
if (isAudioMuted != null) {
this.isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
}
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this);
if (tapBackActivity == null) {
String targetActivityName = intent.getExtras().getString(ACTIVITY_DATA_KEY);
Class<? extends Activity> targetActivity = null;
try {
targetActivity = Class.forName(targetActivityName).asSubclass(Activity.class);
tapBackActivity = targetActivity;
} catch (ClassNotFoundException e) {
JitsiMeetLogger.w(TAG + " Could not find target Activity: " + targetActivityName);
}
}
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, this, tapBackActivity);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
@@ -206,9 +225,6 @@ public class JitsiMeetOngoingConferenceService extends Service
}
}
final String actionName = intent.getAction();
final Action action = Action.fromName(actionName);
// When starting the service, there is no action passed in the intent
if (action != null) {
switch (action) {
@@ -281,8 +297,9 @@ public class JitsiMeetOngoingConferenceService extends Service
@Override
public void onReceive(Context context, Intent intent) {
Class tapBackActivity = JitsiMeetOngoingConferenceService.this.tapBackActivity;
isAudioMuted = Boolean.parseBoolean(intent.getStringExtra("muted"));
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context);
Notification notification = OngoingNotification.buildOngoingConferenceNotification(isAudioMuted, context, tapBackActivity);
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't update service, notification is null");

View File

@@ -73,14 +73,13 @@ class OngoingNotification {
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context) {
static Notification buildOngoingConferenceNotification(Boolean isMuted, Context context, Class tapBackActivity) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
Intent notificationIntent = new Intent(context, context.getClass());
Intent notificationIntent = new Intent(context, tapBackActivity == null ? context.getClass() : tapBackActivity);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, ONGOING_CONFERENCE_CHANNEL_ID);

View File

@@ -205,18 +205,6 @@ class ReactInstanceManagerHolder {
? reactContext.getNativeModule(nativeModuleClass) : null;
}
/**
* Gets the current {@link Activity} linked to React Native.
*
* @return An activity attached to React Native.
*/
static Activity getCurrentActivity() {
ReactContext reactContext
= reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null;
return reactContext != null ? reactContext.getCurrentActivity() : null;
}
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}

View File

@@ -43,7 +43,6 @@ import {
p2pStatusChanged
} from './react/features/base/conference/actions';
import {
AVATAR_URL_COMMAND,
CONFERENCE_LEAVE_REASONS,
EMAIL_COMMAND
} from './react/features/base/conference/constants';
@@ -182,7 +181,6 @@ window.JitsiMeetScreenObtainer = {
* Known custom conference commands.
*/
const commands = {
AVATAR_URL: AVATAR_URL_COMMAND,
CUSTOM_ROLE: 'custom-role',
EMAIL: EMAIL_COMMAND,
ETHERPAD: 'etherpad'
@@ -1772,17 +1770,6 @@ export default {
}));
});
room.addCommandListener(
this.commands.defaults.AVATAR_URL,
(data, from) => {
APP.store.dispatch(
participantUpdated({
conference: room,
id: from,
avatarURL: data.value
}));
});
room.on(
JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
({ audio, video }) => {

View File

@@ -232,9 +232,26 @@ var config = {
// Sets the preferred resolution (height) for local video. Defaults to 720.
// resolution: 720,
// DEPRECATED. Please use raisedHands.disableRemoveRaisedHandOnFocus instead.
// Specifies whether the raised hand will hide when someone becomes a dominant speaker or not
// disableRemoveRaisedHandOnFocus: false,
// Specifies which raised hand related config should be set.
// raisedHands: {
// // Specifies whether the raised hand can be lowered by moderator.
// disableLowerHandByModerator: false,
// // Specifies whether there is a notification before hiding the raised hand
// // when someone becomes the dominant speaker.
// disableLowerHandNotification: true,
// // Specifies whether there is a notification when you are the next speaker in line.
// disableNextSpeakerNotification: false,
// // Specifies whether the raised hand will hide when someone becomes a dominant speaker or not.
// disableRemoveRaisedHandOnFocus: false,
// },
// speakerStats: {
// // Specifies whether the speaker stats is enable or not.
// disabled: false,
@@ -1549,6 +1566,17 @@ var config = {
// and will automatically redirect to the token service to get the token for the meeting.
// tokenAuthUrlAutoRedirect: false
// You can put an array of values to target different entity types in the invite dialog.
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"
// peopleSearchQueryTypes: ["user", "email"],
// Directory endpoint which is called for invite dialog autocomplete
// peopleSearchUrl: "https://myservice.com/api/people",
// Endpoint which is called to send invitation requests
// inviteServiceUrl: "https://myservice.com/api/invite",
// For external entities (e. g. email), the localStorage key holding the token value for directory authentication
// peopleSearchTokenLocation: "mytoken",
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -1565,8 +1593,6 @@ var config = {
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
*/
@@ -1747,8 +1773,6 @@ var config = {
// tileTime: 5000,
// // Limit results by rating: g, pg, pg-13, r. Default value: g.
// rating: 'pg',
// // The proxy server url for giphy requests in the web app.
// proxyUrl: 'https://giphy-proxy.example.com',
// },
// Logging

View File

@@ -69,6 +69,11 @@
window.indexLoadedTime = window.performance.now();
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
window.addEventListener('load', function() {
window.loadedEventTime = window.performance.now();
console.log("(TIME) window loaded event:\t", loadedEventTime);
});
// XXX the code below listeners for errors and displays an error message
// in the document body when any of the required files fails to load.
// The intention is to prevent from displaying broken page.

View File

@@ -1022,7 +1022,7 @@ PODS:
- React-Core
- react-native-performance (5.0.0):
- React-Core
- react-native-safe-area-context (4.7.1):
- react-native-safe-area-context (4.10.8):
- React-Core
- react-native-slider (4.4.3):
- React-Core
@@ -1034,7 +1034,7 @@ PODS:
- react-native-video/Video (6.0.0-alpha.11):
- PromisesSwift
- React-Core
- react-native-webrtc (124.0.3):
- react-native-webrtc (124.0.4):
- JitsiWebRTC (~> 124.0.0)
- React-Core
- react-native-webview (13.8.7):
@@ -1214,7 +1214,7 @@ PODS:
- React-Core
- RNDeviceInfo (10.9.0):
- React-Core
- RNGestureHandler (2.17.1):
- RNGestureHandler (2.18.1):
- glog
- RCT-Folly (= 2022.05.16.00)
- React-Core
@@ -1558,11 +1558,11 @@ SPEC CHECKSUMS:
react-native-orientation-locker: 4409c5b12b65f942e75449872b4f078b6f27af81
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
react-native-webrtc: 41f6602490c618e3ec0e2108e46b84e2c731c127
react-native-webrtc: 48295e7228279470c4f5acb38570e170723bd3b2
react-native-webview: a9663e81c7acc865b88632e43a164838f00745f6
React-nativeconfig: 2e44d0d2dd222b12a5183f4bcaa4a91881497acb
React-NativeModulesApple: 464a9590389efd364d45d726a35ef7ade6b5c59a
@@ -1589,7 +1589,7 @@ SPEC CHECKSUMS:
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
RNGestureHandler: 67d3f1f69d4d0c98d6e83f4229e3bbf997d1dc72
RNGestureHandler: 1155b1898ceddefeebf77792927360d44fe11e77
RNGoogleSignin: a6a612cce56a45ab701c5c5c6e36f5390522d100
RNScreens: e842cdccb23c0a084bd6307f6fa83fd1c1738029
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852

View File

@@ -1,14 +1,12 @@
#!/bin/bash
set -e -u
set -e -u -x
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Lite-Info.plist)
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
DO_GIT_TAG=${GIT_TAG:-0}
echo "Releasing Jitsi Meet SDK Lite ${SDK_VERSION}"
@@ -50,9 +48,6 @@ xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-output ios/sdk/out/JitsiMeetSDK.xcframework
if [[ $DO_GIT_TAG == 1 ]]; then
git tag ios-sdk-lite-${SDK_VERSION}
fi
popd
pushd ${RELEASE_REPO}
@@ -61,11 +56,9 @@ pushd ${RELEASE_REPO}
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework lite/Frameworks/
# Add all files to git
if [[ $DO_GIT_TAG == 1 ]]; then
git add -A .
git commit -m "${SDK_VERSION} lite"
git tag "${SDK_VERSION}-lite"
fi
git add -A .
git commit --allow-empty -m "${SDK_VERSION} lite"
git tag "${SDK_VERSION}-lite"
popd

View File

@@ -1,14 +1,12 @@
#!/bin/bash
set -e -u
set -e -u -x
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
DO_GIT_TAG=${GIT_TAG:-0}
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
@@ -50,9 +48,6 @@ xcodebuild -create-xcframework \
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
-output ios/sdk/out/JitsiMeetSDK.xcframework
if [[ $DO_GIT_TAG == 1 ]]; then
git tag ios-sdk-${SDK_VERSION}
fi
popd
pushd ${RELEASE_REPO}
@@ -61,11 +56,9 @@ pushd ${RELEASE_REPO}
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework Frameworks/
# Add all files to git
if [[ $DO_GIT_TAG == 1 ]]; then
git add -A .
git commit -m "${SDK_VERSION}"
git tag ${SDK_VERSION}
fi
git add -A .
git commit --allow-empty -m "${SDK_VERSION}"
git tag "${SDK_VERSION}"
popd

View File

@@ -2,4 +2,4 @@
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
exec ${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command --reset-cache
exec ${THIS_DIR}/../../node_modules/react-native/scripts/packager.sh --reset-cache

View File

@@ -820,8 +820,11 @@
"videoUnmuteBlockedDescription": "Die Kamera und Bildschirmfreigabe kann aus Überlastungsschutzgründen temporär nicht eingeschaltet werden.",
"videoUnmuteBlockedTitle": "Kamera und Bildschirmfreigabe kann nicht aktiviert werden!",
"viewLobby": "Lobby ansehen",
"viewParticipants": "Personen anzeigen",
"viewVisitors": "Gäste anzeigen",
"waitingParticipants": "{{waitingParticipants}} Personen",
"waitingVisitors": "In der Lobby wartende Gäste: {{waitingVisitors}}",
"waitingVisitorsTitle": "Die Konferenz wurde noch nicht gestartet!",
"whiteboardLimitDescription": "Bitte speichern Sie Ihre Inhalte, da das Nutzungslimit bald erreicht wird und dann Ihr Whiteboard geschlossen wird.",
"whiteboardLimitTitle": "Whiteboard-Nutzung"
},
@@ -835,7 +838,10 @@
"audioModeration": "Für sich selbst die Stummschaltung aufzuheben",
"blockEveryoneMicCamera": "Kamera und Mikrofon von allen sperren",
"breakoutRooms": "Breakout-Räume",
"goLive": "Live gehen",
"invite": "Person einladen",
"lowerAllHands": "Alle Hände senken",
"lowerHand": "Hand senken",
"moreModerationActions": "Weitere Moderationsoptionen",
"moreModerationControls": "Weitere Moderationsoptionen",
"moreParticipantOptions": "Mehr Optionen für Anwesende",
@@ -852,6 +858,7 @@
"headings": {
"lobby": "Lobby ({{count}})",
"participantsList": "Anwesende ({{count}})",
"visitorInQueue": " (Wartende Gäste {{count}})",
"visitorRequests": " (Anfragen {{count}})",
"visitors": "Gäste ({{count}})",
"waitingLobby": "In der Lobby ({{count}})"
@@ -865,10 +872,13 @@
"pinnedParticipant": "Die Person ist angeheftet",
"polls": {
"answer": {
"edit": "Bearbeiten",
"send": "Senden",
"skip": "Überspringen",
"submit": "Speichern"
},
"by": "Von {{ name }}",
"closeButton": "Umfrage schließen",
"create": {
"addOption": "Antwort hinzufügen",
"answerPlaceholder": "Antwort {{index}}",
@@ -878,7 +888,8 @@
"pollQuestion": "Frage",
"questionPlaceholder": "Eine Frage stellen",
"removeOption": "Antwort entfernen",
"send": "Erstellen"
"save": "Erstellen",
"send": "Senden"
},
"errors": {
"notUniqueOption": "Optionen müssen einzigartig sein"
@@ -1483,16 +1494,22 @@
},
"visitors": {
"chatIndicator": "(Gast)",
"joinMeeting": {
"description": "Sie beobachten derzeit diese Konferenz.",
"raiseHand": "Hand heben",
"title": "Konferenz wird beigetreten",
"wishToSpeak": "Wenn Sie sprechen möchten, heben Sie bitte unten Ihre Hand und warten Sie auf die Zustimmung der Moderation"
},
"labelTooltip": "Anzahl Gäste: {{count}}",
"notification": {
"demoteDescription": "Hierhin verschoben von {{actor}}, bitte melden Sie sich um teilzunehmen",
"description": "Bitte melden Sie sich um teilzunehmen",
"noMainParticipantsDescription": "Eine Person muss die Konferenz starten. Bitte versuchen Sie es gleich noch einmal.",
"noMainParticipantsTitle": "Diese Konferenz wurde noch nicht gestartet.",
"noVisitorLobby": "Sie können nicht teilnehmen, solange die Lobby für diese Konferenz aktiviert ist.",
"notAllowedPromotion": "Eine Person muss Ihre Anfrage erst erlauben.",
"title": "Sie sind Gast in der Konferenz"
}
},
"waitingMessage": "Sie werden der Konferenz beitreten, sobald sie gestartet ist!"
},
"volumeSlider": "Lautstärkeregler",
"welcomepage": {

View File

@@ -341,7 +341,7 @@
"liveStreamingDisabledBecauseOfActiveRecordingTooltip": "Nav iespējams, kamēr ir aktīva ierakstīšana",
"localUserControls": "Lokālo lietotāju kontroles",
"lockMessage": "Neizdevās aizslēgt sapulci.",
"lockRoom": "Pievienot sapulci $t(lockRoomPasswordUppercase)",
"lockRoom": "Iestatīt sapulces $t(lockRoomPasswordUppercase)",
"lockTitle": "Aizslēgšāna neizdevās",
"login": "Pierakstīties",
"loginQuestion": "Vai tiešām vēlaties pierakstīties un pamest sapulci?",
@@ -382,9 +382,9 @@
"noDropboxToken": "Nav derīga Dropbox tokena",
"password": "Parole",
"passwordLabel": "Dalībnieks ir aizslēdzis sapulci. Lūdzu, ievadiet $t(lockRoomPassword), lai pievienotos.",
"passwordNotSupported": "Sapulces $t(lockRoomPassword) iestatīšana netiek atbalstīta.",
"passwordNotSupportedTitle": "$t(lockRoomPasswordUppercase) netiek atbalstīts",
"passwordRequired": "Nepieciešams $t(lockRoomPasswordUppercase)",
"passwordNotSupported": "Sapulces slēgšana ar $t(lockRoomPassword) netiek atbalstīta.",
"passwordNotSupportedTitle": "Slēgšana ar $t(lockRoomPasswordUppercase) netiek atbalstīta",
"passwordRequired": "Nepieciešams ievadīt $t(lockRoomPasswordUppercase)",
"permissionCameraRequiredError": "Lai piedalītos konferencēs ar video, ir nepieciešama kameras atļauja. Lūdzu, piešķiriet to Iestatījumos",
"permissionErrorTitle": "Nepieciešama atļauja",
"permissionMicRequiredError": "Lai piedalītos konferencēs ar audio, nepieciešama mikrofona atļauja. Lūdzu, piešķiriet to Iestatījumos",
@@ -567,7 +567,7 @@
"noRoom": "Iezvana numuram nav piesaistīta neviena sapulces telpa.",
"noWhiteboard": "Nevarēja ielādēt tāfeli.",
"numbers": "Iezvana numuri",
"password": "$t(lockRoomPasswordUppercase):",
"password": "Ievadiet $t(lockRoomPasswordUppercase):",
"reachedLimit": "Jūs esat sasniedzis sava plāna limitu.",
"sip": "SIP adrese",
"sipAudioOnly": "Tikai SIP audio adrese",
@@ -604,7 +604,7 @@
"showSpeakerStats": "Rādīt prezentētāja statistiku",
"toggleChat": "Tērzētava (čats) (atvērt/aizvērt)",
"toggleFilmstrip": "Kinolente (rādīt/nerādīt)",
"toggleParticipantsPane": "Rādīt vai paslēpt dalībnieku paneli",
"toggleParticipantsPane": "Rādīt/paslēpt dalībnieku paneli",
"toggleScreensharing": "Pārslēgties starp kameru un ekrāna rādīšanu",
"toggleShortcuts": "Atrās piekļuves taustiņi (rādīt/nerādīt)",
"videoMute": "Kamera (iesl./izsl.)"
@@ -659,7 +659,7 @@
"emailField": "Ievadiet savu e-pasta adresi",
"enableDialogPasswordField": "Iestatīt paroli (neobligāti)",
"enableDialogSubmit": "Iespējot",
"enableDialogText": "Vestibila režīms ļauj aizsargāt sapulci, ļaujot cilvēkiem tajā iekļūt tikai pēc oficiāla moderatora apstiprinājuma.",
"enableDialogText": "Vestibila režīms ļauj aizsargāt sapulci, ļaujot cilvēkiem tajā iekļūt tikai pēc moderatora apstiprinājuma.",
"enterPasswordButton": "Ievadiet sapulces paroli",
"enterPasswordTitle": "Ievadiet paroli, lai pievienotos sapulcei",
"errorMissingPassword": "Lūdzu, ievadiet sapulces paroli",
@@ -719,8 +719,8 @@
"wait": "Lūdzu, uzgaidiet, kamēr jūsu ieraksts tiek saglabāts",
"yes": "Jā"
},
"lockRoomPassword": "parole",
"lockRoomPasswordUppercase": "Parole",
"lockRoomPassword": "paroli",
"lockRoomPasswordUppercase": "Paroli",
"lonelyMeetingExperience": {
"button": "Uzaiciniet citus",
"youAreAlone": "Jūs esat vienīgais sapulcē"
@@ -786,6 +786,7 @@
"newDeviceAction": "Izmantot",
"newDeviceAudioTitle": "Atrasta jauna audio ierīce",
"newDeviceCameraTitle": "Atrasta jauna kamera",
"nextToSpeak": "Jūs esat nākamais runātājs rindā",
"noiseSuppressionDesktopAudioDescription": "Darbvirsmas audio koplietošanas laikā nevar iespējot trokšņu slāpēšanu. Lūdzu, atspējojiet to un mēģiniet vēlreiz.",
"noiseSuppressionFailedTitle": "Neizdevās sākt trokšņu slāpēšanu",
"noiseSuppressionStereoDescription": "Stereo audio trokšņu slāpēšana pašlaik netiek atbalstīta.",
@@ -820,8 +821,11 @@
"videoUnmuteBlockedDescription": "Kameras ieslēgšanas un darbvirsmas koplietošanas darbība ir īslaicīgi bloķēta sistēmas ierobežojumu dēļ.",
"videoUnmuteBlockedTitle": "Kameras ieslēgšana un darbvirsmas koplietošana ir bloķēta!",
"viewLobby": "Skatīt vestibilu",
"viewParticipants": "Skatīt dalībniekus",
"viewVisitors": "Skatīt apmeklētājus",
"waitingParticipants": "{{waitingParticipants}} personas",
"waitingVisitors": "Apmeklētāji gaida rindā: {{waitingVisitors}}",
"waitingVisitorsTitle": "Sanāksme vēl nav sākusies!",
"whiteboardLimitDescription": "Lūdzu, saglabājiet savu progresu, jo drīz tiks sasniegts lietotāju limits un tāfele tiks aizvērta.",
"whiteboardLimitTitle": "Tāfeles lietošana"
},
@@ -835,7 +839,10 @@
"audioModeration": "Ieslēgt savu skaņu",
"blockEveryoneMicCamera": "Bloķēt visiem mikrofonu un kameru",
"breakoutRooms": "Grupu istabas",
"goLive": "Sākt",
"invite": "Uzaicināt",
"lowerAllHands": "Nolaist visas paceltās rokas",
"lowerHand": "Nolaist roku",
"moreModerationActions": "Vairāk moderēšanas iespēju",
"moreModerationControls": "Vairāk moderēšanas iespēju",
"moreParticipantOptions": "Vairāk dalībnieku iespēju",
@@ -852,6 +859,7 @@
"headings": {
"lobby": "Vestibils ({{count}})",
"participantsList": "Sapulces dalībnieki ({{count}})",
"visitorInQueue": " (gaida {{count}})",
"visitorRequests": " (pieprasījumi {{count}})",
"visitors": "Apmeklētāji ({{count}})",
"waitingLobby": "Gaida vestibilā ({{count}})"
@@ -871,6 +879,7 @@
"submit": "Iesniegt"
},
"by": "Pēc {{ name }} iniciatīvas",
"closeButton": "Slēgt aptauju",
"create": {
"addOption": "Pievienot opciju",
"answerPlaceholder": "Opcija {{index}}",
@@ -1026,7 +1035,7 @@
"localRecordingStartWarningTitle": "Apturiet ierakstīšanu, lai to saglabātu",
"localRecordingVideoStop": "Apturot vide, tiks apturēta arī lokālā ierakstīšana. Vai tiešām vēlaties turpināt?",
"localRecordingVideoWarning": "Lai ierakstītu video, tas ir jāieslēdz, uzsākot ierakstīšanu",
"localRecordingWarning": "Noteikti atlasiet pašreizējo cilni, lai izmantotu pareizo video un audio. Ieraksts pašlaik ir ierobežots līdz 1 GB, kas ir aptuveni 100 minūtes.",
"localRecordingWarning": "Noteikti izvēlieties pašreizējo cilni, lai izmantotu pareizo video un audio. Ieraksts pašlaik ir ierobežots līdz 1 GB, kas ir aptuveni 100 minūtes.",
"loggedIn": "Pierakstījies kā {{userName}}",
"noMicPermission": "Mikrofona ierakstu nevarēja izveidot. Lūdzu, piešķiriet atļauju lietot mikrofonu.",
"noStreams": "Nav konstatēta audio vai video straume.",
@@ -1058,8 +1067,8 @@
"pullToRefresh": "Pavilkt, lai atsvaidzinātu"
},
"security": {
"about": "Savai sapulcei pievienojiet $t(lockRoomPassword). Dalībniekiem būs jānorāda $t(lockRoomPassword), lai viņi varētu pievienoties sapulcei.",
"aboutReadOnly": "Moderatora dalībnieki sapulcei var pievienot $t(lockRoomPassword). Dalībniekiem būs jānorāda $t(lockRoomPassword), lai viņi varētu pievienoties sapulcei.",
"about": "Iestatiet sapulcei $t(lockRoomPassword). Dalībniekiem būs jānorāda $t(lockRoomPassword), lai viņi varētu pievienoties sapulcei.",
"aboutReadOnly": "Moderatora dalībnieki sapulcei var iestatīt $t(lockRoomPassword). Dalībniekiem būs jānorāda $t(lockRoomPassword), lai viņi varētu pievienoties sapulcei.",
"insecureRoomNameWarningNative": "Istabas nosaukums nav drošs. Nevēlami dalībnieki var pievienoties jūsu sapulcei. {{recommendAction}} Uzziniet vairāk par tikšanās nodrošināšanu",
"insecureRoomNameWarningWeb": "Istabas nosaukums nav drošs. Nevēlami dalībnieki var pievienoties jūsu sapulcei. {{recommendAction}} Uzziniet vairāk par to, kā nodrošināt atbilstību prasībām <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">šeit</a>.",
"title": "Drošības iespējas",
@@ -1083,7 +1092,7 @@
"desktopShareHighFpsWarning": "Lielāks kadru nomaiņas ātrums darbvirsmas koplietošanai var ietekmēt joslas platumu. Lai jaunie iestatījumi stātos spēkā, ir jārestartē ekrāna kopīgošana.",
"desktopShareWarning": "Lai jaunie iestatījumi stātos spēkā, ir jārestartē ekrāna kopīgošana.",
"devices": "Ierīces",
"followMe": "Visi man seko",
"followMe": "Visi seko man",
"framesPerSecond": "kadri sekundē",
"incomingMessage": "Ienākošā ziņa",
"language": "Valoda",
@@ -1108,7 +1117,7 @@
"selfView": "Pašskats",
"shortcuts": "Īsceļi",
"speakers": "Skaļruņi",
"startAudioMuted": "Dalībnieki pievienojas ar izslēgtu skaņu",
"startAudioMuted": "Dalībnieki pievienojas ar izslēgtu mikrofonu",
"startReactionsMuted": "Izslēgt reakcijas skaņas visiem",
"startVideoMuted": "Dalībnieki pievienojas ar izslēgtu kameru",
"talkWhileMuted": "Runā, kad izslēgta skaņa",
@@ -1486,16 +1495,22 @@
},
"visitors": {
"chatIndicator": "(apmeklētājs)",
"joinMeeting": {
"description": "Jūs pašlaik esat novērotājs šajā konferencē.",
"raiseHand": "Pacelt roku",
"title": "Pievienošanās sapulcei",
"wishToSpeak": "Ja vēlaties runāt, lūdzu, paceliet roku zemāk un gaidiet moderatora apstiprinājumu."
},
"labelTooltip": "Apmeklētāju skaits: {{count}}",
"notification": {
"demoteDescription": "{{actor}} pārveidoja par apmeklētāju, paceliet roku, lai piedalītos",
"description": "Paceliet roku, lai piedalītos",
"noMainParticipantsDescription": "Dalībniekam ir jāsāk sapulce. Lūdzu, pēc brīža mēģiniet vēlreiz.",
"noMainParticipantsTitle": "Šī sapulce vēl nav sākusies.",
"noVisitorLobby": "Jūs nevarat pievienoties, kamēr sapulcei ir iespējots vestibils.",
"notAllowedPromotion": "Dalībniekam vispirms ir jāatļauj jūsu pieprasījums.",
"title": "Jūs esat sapulces apmeklētājs"
}
},
"waitingMessage": "Jūs pievienosities sapulcei, tiklīdz tā sāksies!"
},
"volumeSlider": "Skaļuma slīdnis",
"welcomepage": {

View File

@@ -128,6 +128,7 @@
"privateNotice": "Mensagem privada para {{recipient}}",
"sendButton": "Enviar",
"smileysPanel": "Painel de Emojis",
"systemDisplayName": "Sistema",
"tabs": {
"chat": "Chat",
"polls": "Sondagens"
@@ -263,6 +264,7 @@
"Share": "Partilhar",
"Submit": "Submeter",
"WaitForHostMsg": "A conferência ainda não começou porque ainda não chegaram moderadores. Se quiser ser um moderador, inicie a sessão. Caso contrário, aguarde.",
"WaitForHostNoAuthMsg": "A conferência ainda não começou porque ainda não chegaram os moderadores. Por favor, aguarde.",
"WaitingForHostButton": "Esperar pelo moderador",
"WaitingForHostTitle": "À espera de um moderador...",
"Yes": "Sim",
@@ -318,6 +320,7 @@
"embedMeeting": "Embutir reunião",
"enterDisplayName": "Digite o seu nome",
"error": "Erro",
"errorRoomCreationRestriction": "Tentou juntar-se demasiado depressa, por favor volte mais tarde.",
"gracefulShutdown": "O nosso serviço está atualmente em manutenção. Por favor, tente novamente mais tarde.",
"grantModeratorDialog": "Tem a certeza que quer conceder direitos de moderador a {{participantName}}?",
"grantModeratorTitle": "Conceder direitos de moderador",
@@ -733,8 +736,10 @@
"connectedOneMember": "{{name}} entrou na reunião",
"connectedThreePlusMembers": "{{name}} e muitos outros entraram na reunião",
"connectedTwoMembers": "{{first}} e {{second}} entraram na reunião",
"dataChannelClosed": "Deficiência na qualidade do vídeo",
"dataChannelClosedDescription": "O canal de ponte foi desconectado e, portanto, a qualidade do vídeo está limitada à sua configuração mais baixa.",
"dataChannelClosed": "A qualidade do vídeo pode ser afetada",
"dataChannelClosedDescription": "O canal de ponte está em baixo e, por isso, a qualidade de vídeo pode estar limitada à sua definição mais baixa.",
"dataChannelClosedDescriptionWithAudio": "O canal de ponte está em baixo, pelo que podem ocorrer interrupções no áudio e no vídeo.",
"dataChannelClosedWithAudio": "A qualidade do áudio e do vídeo pode ser afetada",
"disabledIframe": "A incorporação destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos.",
"disabledIframeSecondary": "A incorporação de {{domain}} destina-se apenas a fins de demonstração, pelo que esta chamada será desligada em {{timeout}} minutos. Por favor, use <a href='{{jaasDomain}}' rel='noopener noreferrer' target='_blank'>Jitsi as a Service</a> para incorporação em produção!",
"disconnected": "desconectado",
@@ -781,6 +786,7 @@
"newDeviceAction": "Usar",
"newDeviceAudioTitle": "Novo dispositivo de áudio detetado",
"newDeviceCameraTitle": "Nova câmara detetada",
"nextToSpeak": "É o próximo na fila para falar",
"noiseSuppressionDesktopAudioDescription": "A supressão de ruído não pode ser ativada enquanto se partilha o áudio do ambiente de trabalho, por favor desative-o e tente novamente.",
"noiseSuppressionFailedTitle": "Falha ao iniciar a supressão de ruído",
"noiseSuppressionStereoDescription": "A supressão do ruído de áudio estéreo não é atualmente suportada.",
@@ -815,8 +821,11 @@
"videoUnmuteBlockedDescription": "A operação de ligar a câmara e partilhar o ambiente de trabalho foi temporariamente bloqueada devido aos limites do sistema.",
"videoUnmuteBlockedTitle": "Está bloqueado ligar a câmara e partilhar o ambiente de trabalho!",
"viewLobby": "Ver sala de espera",
"viewParticipants": "Ver participantes",
"viewVisitors": "Ver visitantes",
"waitingParticipants": "{{waitingParticipants}} pessoas",
"waitingVisitors": "Visitantes em fila de espera: {{waitingVisitors}}",
"waitingVisitorsTitle": "A reunião ainda não está em direto!",
"whiteboardLimitDescription": "Guarde o seu progresso, pois o limite de utilizadores será atingido em breve e o quadro branco será encerrado.",
"whiteboardLimitTitle": "Utilização do quadro branco"
},
@@ -830,7 +839,10 @@
"audioModeration": "Ligar o microfone deles",
"blockEveryoneMicCamera": "Bloquear o microfone e a câmara de todos",
"breakoutRooms": "Salas simultâneas",
"goLive": "Aceder ao vivo",
"invite": "Convidar alguém",
"lowerAllHands": "Baixar todas as mãos",
"lowerHand": "Baixar a mão",
"moreModerationActions": "Mais opções de moderação",
"moreModerationControls": "Mais controlos de moderação",
"moreParticipantOptions": "Mais opções de participantes",
@@ -847,6 +859,7 @@
"headings": {
"lobby": "Sala de espera ({{count}})",
"participantsList": "Participantes da reunião ({{count}})",
"visitorInQueue": " (à espera {{count}})",
"visitorRequests": " (pedidos {{count}})",
"visitors": "Visitantes ({{count}})",
"waitingLobby": "Aguardam na sala de espera ({{count}})"
@@ -860,10 +873,13 @@
"pinnedParticipant": "O participante está afixado",
"polls": {
"answer": {
"edit": "Editar",
"send": "Enviar",
"skip": "Ignorar",
"submit": "Submeter"
},
"by": "Por {{ name }}",
"closeButton": "Fechar sondagem",
"create": {
"addOption": "Adicionar opção",
"answerPlaceholder": "Opção {{index}}",
@@ -873,6 +889,7 @@
"pollQuestion": "Pergunta de Sondagem",
"questionPlaceholder": "Faça uma pergunta",
"removeOption": "Remover opção",
"save": "Guardar",
"send": "Enviar"
},
"errors": {
@@ -1478,12 +1495,22 @@
},
"visitors": {
"chatIndicator": "(visitante)",
"joinMeeting": {
"description": "Atualmente, é um observador nesta conferência.",
"raiseHand": "Levantar a mão",
"title": "Participar na reunião",
"wishToSpeak": "Se deseja intervir, levante a mão e aguarde a aprovação do moderador."
},
"labelTooltip": "Número de visitantes: {{count}}",
"notification": {
"demoteDescription": "Enviado aqui pelo {{actor}}, levante a mão para participar",
"description": "Para participar levante a sua mão",
"noMainParticipantsDescription": "Um participante precisa de iniciar a reunião. Tente novamente daqui a pouco.",
"noMainParticipantsTitle": "Esta reunião ainda não começou.",
"noVisitorLobby": "Não é possível aderir enquanto houver uma sala de espera activada para a reunião.",
"notAllowedPromotion": "É necessário que um participante autorize primeiro o seu pedido.",
"title": "É um visitante na reunião"
}
},
"waitingMessage": "Participará na reunião assim que esta estiver em direto!"
},
"volumeSlider": "Controlo de volume",
"welcomepage": {

View File

@@ -443,7 +443,7 @@
"shareVideoTitle": "Share video",
"shareYourScreen": "Share your screen",
"shareYourScreenDisabled": "Screen sharing disabled.",
"sharedVideoDialogError": "Error: Invalid URL",
"sharedVideoDialogError": "Error: Invalid or forbidden URL",
"sharedVideoLinkPlaceholder": "YouTube link or direct video link",
"show": "Show",
"start": "Start ",
@@ -821,6 +821,7 @@
"videoUnmuteBlockedDescription": "Camera unmute and desktop sharing operation have been temporarily blocked because of system limits.",
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
"viewLobby": "View lobby",
"viewParticipants": "View participants",
"viewVisitors": "View visitors",
"waitingParticipants": "{{waitingParticipants}} people",
"waitingVisitors": "Visitors waiting in queue: {{waitingVisitors}}",
@@ -1503,7 +1504,6 @@
"labelTooltip": "Number of visitors: {{count}}",
"notification": {
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
"description": "To participate raise your hand",
"noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.",
"noMainParticipantsTitle": "This meeting hasnt started yet.",
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",

View File

@@ -489,9 +489,8 @@ function initCommands() {
sendAnalytics(createApiEvent('email.changed'));
APP.conference.changeLocalEmail(email);
},
'avatar-url': avatarUrl => {
sendAnalytics(createApiEvent('avatar.url.changed'));
APP.conference.changeLocalAvatarUrl(avatarUrl);
'avatar-url': () => {
console.warn('Setting avatar url is available when using jwt via context.user.avatar');
},
'send-chat-message': (message, to, ignorePrivacy = false) => {
if (to) {
@@ -1126,12 +1125,7 @@ class API {
this.notifyBrowserSupport(isSupportedBrowser());
// Let the embedder know we are ready.
this._sendEvent({
name: 'ready',
// XXX: Here we are using window.config since this is fired really early.
info: window.config.deploymentInfo
});
this._sendEvent({ name: 'ready' });
}
/**
@@ -1343,14 +1337,14 @@ class API {
* @returns {void}
*/
notifyReceivedChatMessage(
{ body, id, nick, privateMessage, ts } = {}) {
if (APP.conference.isLocalId(id)) {
{ body, from, nick, privateMessage, ts } = {}) {
if (APP.conference.isLocalId(from)) {
return;
}
this._sendEvent({
name: 'incoming-message',
from: id,
from,
message: body,
nick,
privateMessage,

181
package-lock.json generated
View File

@@ -61,8 +61,8 @@
"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/v1845.0.0+515a927c/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1852.0.0+526ec25d/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
"null-loader": "4.0.1",
@@ -82,15 +82,15 @@
"react-native-default-preference": "1.4.4",
"react-native-device-info": "10.9.0",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-gesture-handler": "2.17.1",
"react-native-gesture-handler": "2.18.1",
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-immersive-mode": "2.0.2",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "1.6.0",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-safe-area-context": "4.7.1",
"react-native-safe-area-context": "4.10.8",
"react-native-screens": "3.32.0",
"react-native-sound": "0.11.2",
"react-native-splash-screen": "3.3.0",
@@ -100,7 +100,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "124.0.3",
"react-native-webrtc": "124.0.4",
"react-native-webview": "13.8.7",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
@@ -132,7 +132,7 @@
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/js-md5": "0.4.3",
"@types/lodash": "4.14.182",
"@types/lodash-es": "4.17.12",
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
"@types/pixelmatch": "5.2.5",
@@ -6254,6 +6254,15 @@
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/long": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
@@ -10278,9 +10287,9 @@
"dev": true
},
"node_modules/fast-loops": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.3.tgz",
"integrity": "sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g=="
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz",
"integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg=="
},
"node_modules/fast-shallow-equal": {
"version": "1.0.0",
@@ -12489,8 +12498,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1845.0.0+515a927c/lib-jitsi-meet.tgz",
"integrity": "sha512-UzT6skfD8wqs2695oMi3B8SxcImhG2SIYwc1xM+z9pVlJ4MAOWsLc7flCBXSnpnUTK3b5AjCOot3cRqeMibNcQ==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1852.0.0+526ec25d/lib-jitsi-meet.tgz",
"integrity": "sha512-zFt1dzJfSbLejjZUR0FAsGxQIzF/RnLqFmxnHDACaiEIbZhxmyw/Y4rQjLO7FRrmJNSP37U4cuiTi5m/AwP2PA==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -12824,7 +12833,13 @@
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
@@ -15076,9 +15091,9 @@
}
},
"node_modules/react-devtools-core/node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"engines": {
"node": ">=8.3.0"
},
@@ -15307,9 +15322,9 @@
}
},
"node_modules/react-native-gesture-handler": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.17.1.tgz",
"integrity": "sha512-pWfniN6NuVKUq40KACuD3NCMe+bWNQCpD3cmxL6aLSCTwPKYmf7l4Lp0/E/almpjvxhybJZtFLU0w4tmxnIKaA==",
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.18.1.tgz",
"integrity": "sha512-WF2fxQ5kTaxHghlkBM4YxO86SyGWVwrSNgJ1E8z/ZtL2xD5B3bg5agvuVFfOzvceC114yq71s6E9vKPz94ZxRw==",
"license": "MIT",
"dependencies": {
"@egjs/hammerjs": "^2.0.17",
@@ -15334,9 +15349,10 @@
}
},
"node_modules/react-native-immersive-mode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.1.tgz",
"integrity": "sha512-2wlL7VIHl4rr4gwgnUp9K1UvsN7J5VCGqoAvBWQXvB4xn7XaoDEl6z9vqaqOiEdC6aAh2d/7zqcJz+dfcR2ELw==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.2.tgz",
"integrity": "sha512-W4YBpf/seURr6pdt91bKWtlQhwoZYgQN1nllj/y+EkwKs5xbu/IiRxlCs/8ML7dyA8wbOFaXR24AC4HOrvxapw==",
"license": "MIT",
"peerDependencies": {
"react-native": ">=0.60.5"
}
@@ -15350,6 +15366,7 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/react-native-orientation-locker/-/react-native-orientation-locker-1.6.0.tgz",
"integrity": "sha512-D3IOtAcaAi6S2w0Y1EUnr16I47isosQbE7F67fAu9A+gE67NkyKaJ9HL5EsZ+Uc7+7m+NsuBjx3dxuANNy8rVA==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.13.1",
"react-native": ">=0.63.2",
@@ -15365,6 +15382,7 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.2.0.tgz",
"integrity": "sha512-pf9OnL/Tkr+5s4Gjmsn7xh91PtJLDa6qxYa/bmtUhd/+s4cQdWQ8DIFoOFghwZIHHHwVdWtoXkp6HtpjN+r20g==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -15395,9 +15413,10 @@
}
},
"node_modules/react-native-safe-area-context": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.7.1.tgz",
"integrity": "sha512-X2pJG2ttmAbiGlItWedvDkZg1T1ikmEDiz+7HsiIwAIm2UbFqlhqn+B1JF53mSxPzdNaDcCQVHRNPvj8oFu6Yg==",
"version": "4.10.8",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.8.tgz",
"integrity": "sha512-Jx1lovhvIdYygg0UsMCBUJN0Wvj9GlA5bbcBLzjZf93uJpNHzaiHC4hR280+sNVK1+/pMHEyEkXVHDZE5JWn0w==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
@@ -15990,10 +16009,9 @@
}
},
"node_modules/react-native-webrtc": {
"version": "124.0.3",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.3.tgz",
"integrity": "sha512-uU+FxcAlBjLQ1EZnw3q+nje8xw+7hwZ7yDpSK4f1OJ25LrLA6fAjgxHCtMn9D20D9tR4+pRkBugBzkfqmboIFg==",
"license": "MIT",
"version": "124.0.4",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.4.tgz",
"integrity": "sha512-ZbhSz1f+kc1v5VE0B84+v6ujIWTHa2fIuocrYzGUIFab7E5izmct7PNHb9dzzs0xhBGqh4c2rUa49jbL+P/e2w==",
"dependencies": {
"base64-js": "1.5.1",
"debug": "4.3.4",
@@ -17579,9 +17597,9 @@
}
},
"node_modules/strophe.js/node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"optional": true,
"engines": {
"node": ">=10.0.0"
@@ -18712,9 +18730,9 @@
}
},
"node_modules/webpack-bundle-analyzer/node_modules/ws": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true,
"engines": {
"node": ">=8.3.0"
@@ -18903,9 +18921,9 @@
}
},
"node_modules/webpack-dev-server/node_modules/ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@@ -19174,9 +19192,9 @@
}
},
"node_modules/ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"dependencies": {
"async-limiter": "~1.0.0"
}
@@ -23400,6 +23418,15 @@
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==",
"dev": true
},
"@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
"integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
"dev": true,
"requires": {
"@types/lodash": "*"
}
},
"@types/long": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz",
@@ -26411,9 +26438,9 @@
"dev": true
},
"fast-loops": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.3.tgz",
"integrity": "sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g=="
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-loops/-/fast-loops-1.1.4.tgz",
"integrity": "sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg=="
},
"fast-shallow-equal": {
"version": "1.0.0",
@@ -27986,8 +28013,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1845.0.0+515a927c/lib-jitsi-meet.tgz",
"integrity": "sha512-UzT6skfD8wqs2695oMi3B8SxcImhG2SIYwc1xM+z9pVlJ4MAOWsLc7flCBXSnpnUTK3b5AjCOot3cRqeMibNcQ==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1852.0.0+526ec25d/lib-jitsi-meet.tgz",
"integrity": "sha512-zFt1dzJfSbLejjZUR0FAsGxQIzF/RnLqFmxnHDACaiEIbZhxmyw/Y4rQjLO7FRrmJNSP37U4cuiTi5m/AwP2PA==",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
@@ -28250,7 +28277,13 @@
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"lodash.clonedeep": {
"version": "4.5.0",
@@ -29855,9 +29888,9 @@
},
"dependencies": {
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q=="
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="
}
}
},
@@ -30105,9 +30138,9 @@
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A=="
},
"react-native-gesture-handler": {
"version": "2.17.1",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.17.1.tgz",
"integrity": "sha512-pWfniN6NuVKUq40KACuD3NCMe+bWNQCpD3cmxL6aLSCTwPKYmf7l4Lp0/E/almpjvxhybJZtFLU0w4tmxnIKaA==",
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.18.1.tgz",
"integrity": "sha512-WF2fxQ5kTaxHghlkBM4YxO86SyGWVwrSNgJ1E8z/ZtL2xD5B3bg5agvuVFfOzvceC114yq71s6E9vKPz94ZxRw==",
"requires": {
"@egjs/hammerjs": "^2.0.17",
"hoist-non-react-statics": "^3.3.0",
@@ -30124,9 +30157,9 @@
}
},
"react-native-immersive-mode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.1.tgz",
"integrity": "sha512-2wlL7VIHl4rr4gwgnUp9K1UvsN7J5VCGqoAvBWQXvB4xn7XaoDEl6z9vqaqOiEdC6aAh2d/7zqcJz+dfcR2ELw=="
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.2.tgz",
"integrity": "sha512-W4YBpf/seURr6pdt91bKWtlQhwoZYgQN1nllj/y+EkwKs5xbu/IiRxlCs/8ML7dyA8wbOFaXR24AC4HOrvxapw=="
},
"react-native-keep-awake": {
"version": "4.0.0",
@@ -30159,9 +30192,9 @@
"integrity": "sha512-nZJdliNFaA7vmu9PbYGSlwguRQVsuWX8CtchUUxZvjd/i9B3OYYveajIcuic3oQjWyTydLeDlhXRwpOUlj2Npw=="
},
"react-native-safe-area-context": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.7.1.tgz",
"integrity": "sha512-X2pJG2ttmAbiGlItWedvDkZg1T1ikmEDiz+7HsiIwAIm2UbFqlhqn+B1JF53mSxPzdNaDcCQVHRNPvj8oFu6Yg=="
"version": "4.10.8",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.8.tgz",
"integrity": "sha512-Jx1lovhvIdYygg0UsMCBUJN0Wvj9GlA5bbcBLzjZf93uJpNHzaiHC4hR280+sNVK1+/pMHEyEkXVHDZE5JWn0w=="
},
"react-native-screens": {
"version": "3.32.0",
@@ -30518,9 +30551,9 @@
}
},
"react-native-webrtc": {
"version": "124.0.3",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.3.tgz",
"integrity": "sha512-uU+FxcAlBjLQ1EZnw3q+nje8xw+7hwZ7yDpSK4f1OJ25LrLA6fAjgxHCtMn9D20D9tR4+pRkBugBzkfqmboIFg==",
"version": "124.0.4",
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-124.0.4.tgz",
"integrity": "sha512-ZbhSz1f+kc1v5VE0B84+v6ujIWTHa2fIuocrYzGUIFab7E5izmct7PNHb9dzzs0xhBGqh4c2rUa49jbL+P/e2w==",
"requires": {
"base64-js": "1.5.1",
"debug": "4.3.4",
@@ -31603,9 +31636,9 @@
},
"dependencies": {
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"optional": true
}
}
@@ -32381,9 +32414,9 @@
}
},
"ws": {
"version": "7.5.7",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz",
"integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==",
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"dev": true
}
}
@@ -32500,9 +32533,9 @@
}
},
"ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true
}
}
@@ -32683,9 +32716,9 @@
}
},
"ws": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz",
"integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz",
"integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==",
"requires": {
"async-limiter": "~1.0.0"
}

View File

@@ -67,8 +67,8 @@
"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/v1845.0.0+515a927c/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1852.0.0+526ec25d/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
"null-loader": "4.0.1",
@@ -88,15 +88,15 @@
"react-native-default-preference": "1.4.4",
"react-native-device-info": "10.9.0",
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
"react-native-gesture-handler": "2.17.1",
"react-native-gesture-handler": "2.18.1",
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-immersive-mode": "2.0.2",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "1.6.0",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-safe-area-context": "4.7.1",
"react-native-safe-area-context": "4.10.8",
"react-native-screens": "3.32.0",
"react-native-sound": "0.11.2",
"react-native-splash-screen": "3.3.0",
@@ -106,7 +106,7 @@
"react-native-url-polyfill": "2.0.0",
"react-native-video": "6.0.0-alpha.11",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "124.0.3",
"react-native-webrtc": "124.0.4",
"react-native-webview": "13.8.7",
"react-native-youtube-iframe": "2.3.0",
"react-redux": "7.2.9",
@@ -138,7 +138,7 @@
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/js-md5": "0.4.3",
"@types/lodash": "4.14.182",
"@types/lodash-es": "4.17.12",
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
"@types/pixelmatch": "5.2.5",

View File

@@ -11,6 +11,7 @@
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@braintree/sanitize-url": "0.0.0",
"@jitsi/js-utils": "0.0.0",
"@jitsi/logger": "0.0.0",
"@jitsi/rtcstats": "0.0.0",
@@ -19,6 +20,7 @@
"@react-navigation/material-top-tabs": "0.0.0",
"@react-navigation/native": "0.0.0",
"@react-navigation/stack": "0.0.0",
"@stomp/stompjs": "0.0.0",
"@xmldom/xmldom": "0.0.0",
"base64-js": "0.0.0",
"grapheme-splitter": "0.0.0",
@@ -29,7 +31,7 @@
"js-sha512": "0.0.0",
"jwt-decode": "0.0.0",
"lib-jitsi-meet": "0.0.0",
"lodash": "0.0.0",
"lodash-es": "0.0.0",
"moment": "0.0.0",
"moment-duration-format": "0.0.0",
"optional-require": "0.0.0",
@@ -39,6 +41,7 @@
"react-i18next": "0.0.0",
"react-linkify": "0.0.0",
"react-native-dialog": "0.0.0",
"react-native-paper": "0.0.0",
"react-native-svg-transformer": "0.0.0",
"react-native-tab-view": "0.0.0",
"react-native-url-polyfill": "0.0.0",
@@ -46,6 +49,7 @@
"react-redux": "0.0.0",
"redux": "0.0.0",
"redux-thunk": "0.0.0",
"text-encoding": "0.0.0",
"unorm": "0.0.0",
"util": "0.0.0",
"uuid": "0.0.0",
@@ -53,7 +57,6 @@
},
"peerDependencies": {
"@amplitude/react-native": "0.0.0",
"@braintree/sanitize-url": "0.0.0",
"@giphy/react-native-sdk": "0.0.0",
"@react-native/metro-config": "*",
"@react-native-async-storage/async-storage": "0.0.0",
@@ -61,7 +64,6 @@
"@react-native-community/netinfo": "0.0.0",
"@react-native-community/slider": "0.0.0",
"@react-native-google-signin/google-signin": "0.0.0",
"@stomp/stompjs": "0.0.0",
"react-native": "*",
"react": "*",
"react-native-background-timer": "0.0.0",
@@ -73,7 +75,6 @@
"react-native-immersive-mode": "0.0.0",
"react-native-keep-awake": "0.0.0",
"react-native-pager-view": "0.0.0",
"react-native-paper": "0.0.0",
"react-native-performance": "0.0.0",
"react-native-orientation-locker": "0.0.0",
"react-native-safe-area-context": "0.0.0",
@@ -84,8 +85,7 @@
"react-native-video": "0.0.0",
"react-native-watch-connectivity": "0.0.0",
"react-native-webrtc": "0.0.0",
"react-native-webview": "0.0.0",
"text-encoding": "0.0.0"
"react-native-webview": "0.0.0"
},
"overrides": {
"@xmldom/xmldom": "0.0.0"

View File

@@ -1,10 +1,6 @@
const fs = require('fs');
const path = require('path');
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';
@@ -56,44 +52,6 @@ function copyFolderRecursiveSync(source, target) {
}
}
/**
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
*/
function mergeDependencyVersions() {
// Updates SDK dependencies to match project dependencies.
for (const key in SDKPackageJSON.dependencies) {
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
}
}
// Updates SDK peer dependencies.
for (const key in packageJSON.dependencies) {
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
// Updates all peer dependencies except react and react-native.
if (key !== 'react' && key !== 'react-native') {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
}
}
}
// Updates SDK overrides dependencies.
for (const key in packageJSON.overrides) {
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {
SDKPackageJSON.overrides[key] = packageJSON.overrides[key];
}
}
const data = JSON.stringify(SDKPackageJSON, null, 4);
fs.writeFileSync('package.json', data);
}
// TODO: put this in a seperate step
mergeDependencyVersions();
copyFolderRecursiveSync(
'../images',
'.'

View File

@@ -0,0 +1,42 @@
const fs = require('fs');
const packageJSON = require('../package.json');
const SDKPackageJSON = require('./package.json');
/**
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
*/
function mergeDependencyVersions() {
// Updates SDK dependencies to match project dependencies.
for (const key in SDKPackageJSON.dependencies) {
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
}
}
// Updates SDK peer dependencies.
for (const key in packageJSON.dependencies) {
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
// Updates all peer dependencies except react and react-native.
if (key !== 'react' && key !== 'react-native') {
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
}
}
}
// Updates SDK overrides dependencies.
for (const key in packageJSON.overrides) {
if (SDKPackageJSON.overrides.hasOwnProperty(key)) {
SDKPackageJSON.overrides[key] = packageJSON.overrides[key];
}
}
const data = JSON.stringify(SDKPackageJSON, null, 4);
fs.writeFileSync('package.json', data);
}
mergeDependencyVersions();

View File

@@ -76,7 +76,7 @@ export const _getTokenAuthState = (
for (const key of Object.keys(params)) {
// we allow only config and interfaceConfig overrides in the state
if (key.startsWith('config.') || key.startsWith('interfaceConfig.')) {
if (key.startsWith('config.') || key.startsWith('interfaceConfig.') || key.startsWith('iceServers.')) {
// @ts-ignore
state[key] = params[key];
}

View File

@@ -1,6 +1,6 @@
// @ts-expect-error
import { jitsiLocalStorage } from '@jitsi/js-utils';
import _ from 'lodash';
import { isEqual } from 'lodash-es';
import React, { Component, ComponentType, Fragment } from 'react';
import { I18nextProvider } from 'react-i18next';
import { Provider } from 'react-redux';
@@ -254,7 +254,7 @@ export default class BaseApp<P> extends Component<P, IState> {
href?: string;
props?: Object;
}): Promise<any> {
if (_.isEqual(route, this.state.route)) {
if (isEqual(route, this.state.route)) {
return Promise.resolve();
}

View File

@@ -48,6 +48,11 @@ export interface IProps {
*/
colorBase?: string;
/**
* Indicates the default icon for the avatar.
*/
defaultIcon?: string;
/**
* Display name of the entity to render an avatar for (if any). This is handy when we need
* an avatar for a non-participant entity (e.g. A recent list item).
@@ -112,6 +117,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
* @static
*/
static defaultProps = {
defaultIcon: IconUser,
dynamicColor: true
};
@@ -172,6 +178,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
_loadableAvatarUrlUseCORS,
className,
colorBase,
defaultIcon,
dynamicColor,
id,
size,
@@ -229,7 +236,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
}
if (navigator.product !== 'ReactNative') {
avatarProps.iconUser = IconUser;
avatarProps.iconUser = defaultIcon;
}
return (

View File

@@ -1,5 +1,5 @@
import GraphemeSplitter from 'grapheme-splitter';
import _ from 'lodash';
import { split } from 'lodash-es';
const AVATAR_COLORS = [
'#6A50D3',
@@ -63,7 +63,7 @@ function getFirstGraphemeUpper(word: string) {
*/
export function getInitials(s?: string) {
// We don't want to use the domain part of an email address, if it is one
const initialsBasis = _.split(s, '@')[0];
const initialsBasis = split(s, '@')[0];
const [ firstWord, secondWord ] = initialsBasis.split(wordSplitRegex).filter(Boolean);
return getFirstGraphemeUpper(firstWord) + getFirstGraphemeUpper(secondWord);

View File

@@ -72,7 +72,6 @@ import {
} from './actionTypes';
import { setupVisitorStartupMedia } from './actions';
import {
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
JITSI_CONFERENCE_URL_KEY
} from './constants';
@@ -275,13 +274,6 @@ function _addConferenceListeners(conference: IJitsiConference, dispatch: IStore[
botType
})));
conference.addCommandListener(
AVATAR_URL_COMMAND,
(data: { value: string; }, id: string) => dispatch(participantUpdated({
conference,
id,
avatarURL: data.value
})));
conference.addCommandListener(
EMAIL_COMMAND,
(data: { value: string; }, id: string) => dispatch(participantUpdated({

View File

@@ -1,10 +1,3 @@
/**
* The command type for updating a participant's avatar URL.
*
* @type {string}
*/
export const AVATAR_URL_COMMAND = 'avatar-url';
/**
* The command type for updating a participant's email address.
*

View File

@@ -1,5 +1,5 @@
import { sha512_256 as sha512 } from 'js-sha512';
import _ from 'lodash';
import { upperFirst, words } from 'lodash-es';
import { getName } from '../../app/functions';
import { IReduxState, IStore } from '../../app/types';
@@ -22,7 +22,6 @@ import {
import { setObfuscatedRoom } from './actions';
import {
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
JITSI_CONFERENCE_URL_KEY
} from './constants';
@@ -90,7 +89,9 @@ export function commonUserJoinedHandling(
} else {
const isReplacing = user?.isReplacing();
// the identity and avatar come from jwt and never change in the presence
dispatch(participantJoined({
avatarURL: user.getIdentity()?.user?.avatar,
botType: user.getBotType(),
conference,
id,
@@ -540,15 +541,11 @@ export function sendLocalParticipant(
stateful: IStateful,
conference?: IJitsiConference) {
const {
avatarURL,
email,
features,
name
} = getLocalParticipant(stateful) ?? {};
avatarURL && conference?.sendCommand(AVATAR_URL_COMMAND, {
value: avatarURL
});
email && conference?.sendCommand(EMAIL_COMMAND, {
value: email
});
@@ -571,7 +568,7 @@ export function sendLocalParticipant(
* @returns {string}
*/
function safeStartCase(s = '') {
return _.words(`${s}`.replace(/['\u2019]/g, '')).reduce(
(result, word, index) => result + (index ? ' ' : '') + _.upperFirst(word)
return words(`${s}`.replace(/['\u2019]/g, '')).reduce(
(result, word, index) => result + (index ? ' ' : '') + upperFirst(word)
, '');
}

View File

@@ -375,7 +375,6 @@ export interface IConfig {
giphy?: {
displayMode?: 'all' | 'tile' | 'chat';
enabled?: boolean;
proxyUrl?: string;
rating?: 'g' | 'pg' | 'pg-13' | 'r';
sdkKey?: string;
tileTime?: number;
@@ -477,6 +476,7 @@ export interface IConfig {
};
pcStatsInterval?: number;
peopleSearchQueryTypes?: string[];
peopleSearchTokenLocation?: string;
peopleSearchUrl?: string;
preferBosh?: boolean;
preferVisitor?: boolean;
@@ -487,6 +487,12 @@ export interface IConfig {
hideExtraJoinButtons?: Array<string>;
};
prejoinPageEnabled?: boolean;
raisedHands?: {
disableLowerHandByModerator?: boolean;
disableLowerHandNotification?: boolean;
disableNextSpeakerNotification?: boolean;
disableRemoveRaisedHandOnFocus?: boolean;
};
readOnlyName?: boolean;
recordingLimit?: {
appName?: string;

View File

@@ -201,6 +201,7 @@ export default [
'preferVisitor',
'prejoinConfig',
'prejoinPageEnabled',
'raisedHands',
'recordingService',
'requireDisplayName',
'remoteVideoMenu',

View File

@@ -3,7 +3,7 @@ import { jitsiLocalStorage } from '@jitsi/js-utils';
// eslint-disable-next-line lines-around-comment
// @ts-ignore
import { safeJsonParse } from '@jitsi/js-utils/json';
import _ from 'lodash';
import { isEmpty, mergeWith, pick } from 'lodash-es';
import { IReduxState } from '../../app/types';
import { getLocalParticipant } from '../participants/functions';
@@ -89,7 +89,37 @@ export function getFeatureFlag(state: IReduxState, featureFlag: string) {
* @returns {boolean}
*/
export function getDisableRemoveRaisedHandOnFocus(state: IReduxState) {
return state['features/base/config']?.disableRemoveRaisedHandOnFocus || false;
return state['features/base/config']?.raisedHands?.disableRemoveRaisedHandOnFocus || false;
}
/**
* Selector used to get the disableLowerHandByModerator.
*
* @param {Object} state - The global state.
* @returns {boolean}
*/
export function getDisableLowerHandByModerator(state: IReduxState) {
return state['features/base/config']?.raisedHands?.disableLowerHandByModerator || false;
}
/**
* Selector used to get the disableLowerHandNotification.
*
* @param {Object} state - The global state.
* @returns {boolean}
*/
export function getDisableLowerHandNotification(state: IReduxState) {
return state['features/base/config']?.raisedHands?.disableLowerHandNotification || true;
}
/**
* Selector used to get the disableNextSpeakerNotification.
*
* @param {Object} state - The global state.
* @returns {boolean}
*/
export function getDisableNextSpeakerNotification(state: IReduxState) {
return state['features/base/config']?.raisedHands?.disableNextSpeakerNotification || false;
}
/**
@@ -136,13 +166,11 @@ export function overrideConfigJSON(config: IConfig, interfaceConfig: any, json:
const configJSON
= getWhitelistedJSON(configName as 'interfaceConfig' | 'config', json[configName]);
if (!_.isEmpty(configJSON)) {
logger.info(
`Extending ${configName} with: ${
JSON.stringify(configJSON)}`);
if (!isEmpty(configJSON)) {
logger.info(`Extending ${configName} with: ${JSON.stringify(configJSON)}`);
// eslint-disable-next-line arrow-body-style
_.mergeWith(configObj, configJSON, (oldValue, newValue) => {
mergeWith(configObj, configJSON, (oldValue, newValue) => {
// XXX We don't want to merge the arrays, we want to
// overwrite them.
@@ -166,9 +194,9 @@ export function overrideConfigJSON(config: IConfig, interfaceConfig: any, json:
*/
export function getWhitelistedJSON(configName: 'interfaceConfig' | 'config', configJSON: any): Object {
if (configName === 'interfaceConfig') {
return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST);
return pick(configJSON, INTERFACE_CONFIG_WHITELIST);
} else if (configName === 'config') {
return _.pick(configJSON, CONFIG_WHITELIST);
return pick(configJSON, CONFIG_WHITELIST);
}
return configJSON;

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { merge, union } from 'lodash-es';
import { CONFERENCE_INFO } from '../../conference/components/constants';
import { TOOLBAR_BUTTONS } from '../../toolbox/constants';
@@ -194,7 +194,7 @@ function _setConfig(state: IConfig, { config }: { config: IConfig; }) {
});
}
const newState = _.merge(
const newState = merge(
{},
config,
hdAudioOptions,
@@ -397,7 +397,7 @@ function _translateLegacyConfig(oldValue: IConfig) {
= (newValue.conferenceInfo?.alwaysVisible ?? [])
.filter(c => !CONFERENCE_HEADER_MAPPING[key].includes(c));
newValue.conferenceInfo.autoHide
= _.union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
= union(newValue.conferenceInfo.autoHide, CONFERENCE_HEADER_MAPPING[key]);
} else {
newValue.conferenceInfo.alwaysVisible
= (newValue.conferenceInfo.alwaysVisible ?? [])
@@ -442,6 +442,12 @@ function _translateLegacyConfig(oldValue: IConfig) {
newValue.disabledSounds.unshift('INCOMING_MSG_SOUND');
}
newValue.raisedHands = newValue.raisedHands || {};
if (oldValue.disableRemoveRaisedHandOnFocus) {
newValue.raisedHands.disableRemoveRaisedHandOnFocus = oldValue.disableRemoveRaisedHandOnFocus;
}
if (oldValue.stereo || oldValue.opusMaxAverageBitrate) {
newValue.audioQuality = {
opusMaxAverageBitrate: oldValue.audioQuality?.opusMaxAverageBitrate ?? oldValue.opusMaxAverageBitrate,
@@ -593,7 +599,7 @@ function _translateLegacyConfig(oldValue: IConfig) {
* @returns {Object} The new state after the reduction of the specified action.
*/
function _updateConfig(state: IConfig, { config }: { config: IConfig; }) {
const newState = _.merge({}, state, config);
const newState = merge({}, state, config);
_cleanupConfig(newState);

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { cloneDeep } from 'lodash-es';
import { IReduxState, IStore } from '../../app/types';
import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
@@ -113,7 +113,7 @@ export function connectionFailed(
export function constructOptions(state: IReduxState) {
// Deep clone the options to make sure we don't modify the object in the
// redux store.
const options: IOptions = _.cloneDeep(state['features/base/config']);
const options: IOptions = cloneDeep(state['features/base/config']);
const { locationURL, preferVisitor } = state['features/base/connection'];
const params = parseURLParams(locationURL || '');

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { isEqual, merge } from 'lodash-es';
import ReducerRegistry from '../redux/ReducerRegistry';
@@ -25,9 +25,9 @@ export interface IFlagsState {
ReducerRegistry.register<IFlagsState>('features/base/flags', (state = DEFAULT_STATE, action): IFlagsState => {
switch (action.type) {
case UPDATE_FLAGS: {
const newState = _.merge({}, state, action.flags);
const newState = merge({}, state, action.flags);
return _.isEqual(state, newState) ? state : newState;
return isEqual(state, newState) ? state : newState;
}
}

View File

@@ -59,7 +59,7 @@ export default {
function normalizeLanguage(language: string) {
const [ lang, variant ] = language.replace('_', '-').split('-');
if (!variant || lang === variant) {
if (!variant || lang.toUpperCase() === variant.toUpperCase()) {
return lang;
}

View File

@@ -1,7 +1,7 @@
import COUNTRIES_RESOURCES from 'i18n-iso-countries/langs/en.json';
import i18next from 'i18next';
import I18nextXHRBackend, { HttpBackendOptions } from 'i18next-http-backend';
import _ from 'lodash';
import { merge } from 'lodash-es';
import LANGUAGES_RESOURCES from '../../../../lang/languages.json';
import MAIN_RESOURCES from '../../../../lang/main.json';
@@ -22,7 +22,7 @@ const COUNTRIES_RESOURCES_OVERRIDES = {
/**
* Merged country names.
*/
const COUNTRIES = _.merge({}, COUNTRIES_RESOURCES, COUNTRIES_RESOURCES_OVERRIDES);
const COUNTRIES = merge({}, COUNTRIES_RESOURCES, COUNTRIES_RESOURCES_OVERRIDES);
/**
* The available/supported languages.

View File

@@ -46,7 +46,7 @@ export function getJwtName(state: IReduxState) {
* @param {string} feature - The feature we want to check.
* @param {boolean} ifNoToken - Default value if there is no token.
* @param {boolean} ifNotInFeatures - Default value if features prop exists but does not have the {@code feature}.
* @returns {bolean}
* @returns {boolean}
*/
export function isJwtFeatureEnabled(state: IReduxState, feature: string, ifNoToken = false, ifNotInFeatures = false) {
const { jwt } = state['features/base/jwt'];

View File

@@ -1,4 +1,4 @@
import debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
import { IStore } from '../../app/types';
import { SET_FILMSTRIP_ENABLED } from '../../filmstrip/actionTypes';
@@ -11,7 +11,6 @@ import { SET_AUDIO_ONLY } from '../audio-only/actionTypes';
import { CONFERENCE_JOINED } from '../conference/actionTypes';
import { getParticipantById } from '../participants/functions';
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { isLocalVideoTrackDesktop } from '../tracks/functions';
import { setLastN } from './actions';
import logger from './logger';
@@ -45,9 +44,7 @@ const _updateLastN = debounce(({ dispatch, getState }: IStore) => {
// 3. -1 as the default value.
let lastNSelected = config.startLastN ?? (config.channelLastN ?? -1);
if (typeof appState !== 'undefined' && appState !== 'active') {
lastNSelected = isLocalVideoTrackDesktop(state) ? 1 : 0;
} else if (carMode) {
if (appState === 'background' || carMode) {
lastNSelected = 0;
} else if (audioOnly) {
const { remoteScreenShares, tileViewEnabled } = state['features/video-layout'];

View File

@@ -1,6 +1,6 @@
// @ts-expect-error
import Logger, { getLogger as _getLogger } from '@jitsi/logger';
import _ from 'lodash';
import { once } from 'lodash-es';
import LogTransport from './LogTransport';
@@ -26,7 +26,7 @@ export function getLogger(id: string) {
/**
* Initializes native logging. This operations must be done as early as possible.
*/
export const _initLogging = _.once(() => {
export const _initLogging = once(() => {
if (navigator.product !== 'ReactNative') {
return;
}

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { merge } from 'lodash-es';
import { AnyAction } from 'redux';
import ReducerRegistry from '../redux/ReducerRegistry';
@@ -95,7 +95,7 @@ ReducerRegistry.register<ILoggingState>(
* reduction of the specified action.
*/
function _setLoggingConfig(state: ILoggingState, action: AnyAction) {
const newConfig = _.merge({}, DEFAULT_STATE.config, action.config);
const newConfig = merge({}, DEFAULT_STATE.config, action.config);
if (equals(state.config, newConfig)) {
return state;

View File

@@ -547,7 +547,7 @@ export function participantKicked(kicker: any, kicked: any) {
const state = getState();
const localParticipant = getLocalParticipant(state);
const kickedId = kicked.getId();
const kickerId = kicker.getId();
const kickerId = kicker?.getId();
dispatch({
type: PARTICIPANT_KICKED,
@@ -555,7 +555,7 @@ export function participantKicked(kicker: any, kicked: any) {
kicker: kickerId
});
if (kicked.isReplaced?.() || kickerId === localParticipant?.id) {
if (kicked.isReplaced?.() || !kickerId || kickerId === localParticipant?.id) {
return;
}

View File

@@ -8,6 +8,7 @@ import {
isTrackStreamingStatusInactive
} from '../../../connection-indicator/functions';
import SharedVideo from '../../../shared-video/components/native/SharedVideo';
import { isSharedVideoEnabled } from '../../../shared-video/functions';
import { IStateful } from '../../app/types';
import Avatar from '../../avatar/components/Avatar';
import { translate } from '../../i18n/functions';
@@ -236,7 +237,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_isConnectionInactive: isTrackStreamingStatusInactive(videoTrack),
_isSharedVideoParticipant: isSharedVideoParticipant(participant),
_participantName: getParticipantDisplayName(state, participantId),
_renderVideo: shouldRenderParticipantVideo(state, participantId) && !disableVideo,
_renderVideo: shouldRenderParticipantVideo(state, participantId) && !disableVideo
&& isSharedVideoEnabled(state),
_videoTrack: videoTrack
};
}

View File

@@ -14,6 +14,7 @@ import {
NOTIFICATION_TIMEOUT_TYPE,
RAISE_HAND_NOTIFICATION_ID
} from '../../notifications/constants';
import { open as openParticipantsPane } from '../../participants-pane/actions';
import { isForceMuted } from '../../participants-pane/functions';
import { CALLING, INVITED } from '../../presence-status/constants';
import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
@@ -783,10 +784,19 @@ function _raiseHandUpdated({ dispatch, getState }: IStore, conference: IJitsiCon
|| isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
}
const action = shouldDisplayAllowAction ? {
customActionNameKey: [ 'notify.allowAction' ],
customActionHandler: [ () => dispatch(approveParticipant(participantId)) ]
} : {};
let action;
if (shouldDisplayAllowAction) {
action = {
customActionNameKey: [ 'notify.allowAction' ],
customActionHandler: [ () => dispatch(approveParticipant(participantId)) ]
};
} else {
action = {
customActionNameKey: [ 'notify.viewParticipants' ],
customActionHandler: [ () => dispatch(openParticipantsPane()) ]
};
}
if (raisedHandTimestamp) {
let notificationTitle;
@@ -810,7 +820,7 @@ function _raiseHandUpdated({ dispatch, getState }: IStore, conference: IJitsiCon
concatText: true,
uid: RAISE_HAND_NOTIFICATION_ID,
...action
}, shouldDisplayAllowAction ? NOTIFICATION_TIMEOUT_TYPE.MEDIUM : NOTIFICATION_TIMEOUT_TYPE.SHORT));
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
dispatch(playSound(RAISE_HAND_SOUND_ID));
}
}

View File

@@ -118,13 +118,17 @@ ReducerRegistry.register<IParticipantsState>('features/base/participants',
case NOTIFIED_TO_SPEAK: {
return {
...state,
raisedHandsQueue: [
{
...state.raisedHandsQueue[0],
hasBeenNotified: true
},
...state.raisedHandsQueue.slice(1)
]
raisedHandsQueue: state.raisedHandsQueue.map((item, index) => {
if (index === 0) {
return {
...item,
hasBeenNotified: true
};
}
return item;
})
};
}

View File

@@ -1,12 +1,16 @@
import _ from 'lodash';
import { difference } from 'lodash-es';
import { batch } from 'react-redux';
import { IStore } from '../../app/types';
import { showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
import { hideNotification, showNotification } from '../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE, RAISE_HAND_NOTIFICATION_ID } from '../../notifications/constants';
import { getCurrentConference } from '../conference/functions';
import { getSsrcRewritingFeatureFlag, hasBeenNotified, isNextToSpeak } from '../config/functions.any';
import {
getDisableNextSpeakerNotification,
getSsrcRewritingFeatureFlag,
hasBeenNotified,
isNextToSpeak } from '../config/functions.any';
import { VIDEO_TYPE } from '../media/constants';
import StateListenerRegistry from '../redux/StateListenerRegistry';
@@ -33,9 +37,16 @@ StateListenerRegistry.register(
StateListenerRegistry.register(
/* selector */ state => state['features/base/participants'].raisedHandsQueue,
/* listener */ (raisedHandsQueue, store) => {
if (isNextToSpeak(store.getState()) && !hasBeenNotified(store.getState())) {
if (raisedHandsQueue.length
&& isNextToSpeak(store.getState())
&& !hasBeenNotified(store.getState())
&& !getDisableNextSpeakerNotification(store.getState())
&& !store.getState()['features/visitors'].iAmVisitor) { // visitors raise hand to be promoted
_notifyNextSpeakerInRaisedHandQueue(store);
}
if (!raisedHandsQueue[0]) {
store.dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
}
}
);
@@ -54,8 +65,8 @@ function _createOrRemoveVirtualParticipants(
store: IStore): void {
const { dispatch, getState } = store;
const conference = getCurrentConference(getState());
const removedScreenshareSourceNames = _.difference(oldScreenshareSourceNames, newScreenshareSourceNames);
const addedScreenshareSourceNames = _.difference(newScreenshareSourceNames, oldScreenshareSourceNames);
const removedScreenshareSourceNames = difference(oldScreenshareSourceNames, newScreenshareSourceNames);
const addedScreenshareSourceNames = difference(newScreenshareSourceNames, oldScreenshareSourceNames);
if (removedScreenshareSourceNames.length) {
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {

View File

@@ -1,14 +1,21 @@
import React, { Component, ReactNode } from 'react';
import { toArray } from 'react-emoji-render';
import { connect } from 'react-redux';
import { IReduxState } from '../../../../app/types';
import GifMessage from '../../../../chat/components/web/GifMessage';
import { GIF_PREFIX } from '../../../../gifs/constants';
import { isGifMessage } from '../../../../gifs/functions.web';
import { isGifEnabled, isGifMessage } from '../../../../gifs/functions.web';
import Linkify from './Linkify';
interface IProps {
/**
* Whether the gifs are enabled or not.
*/
gifEnabled: boolean;
/**
* The body of the message.
*/
@@ -43,11 +50,11 @@ class Message extends Component<IProps> {
// Tokenize the text in order to avoid emoji substitution for URLs
const tokens = text ? text.split(' ') : [];
const content = [];
const { gifEnabled } = this.props;
// check if the message is a GIF
if (isGifMessage(text)) {
if (gifEnabled && isGifMessage(text)) {
const url = text.substring(GIF_PREFIX.length, text.length - 1);
content.push(<GifMessage
@@ -93,4 +100,16 @@ class Message extends Component<IProps> {
}
}
export default Message;
/**
* Maps part of the redux state to the props of this component.
*
* @param {IReduxState} state - The Redux state.
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
return {
gifEnabled: isGifEnabled(state)
};
}
export default connect(_mapStateToProps)(Message);

View File

@@ -1,4 +1,4 @@
import _debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
import React, { Component } from 'react';
import { MultiSelectItem } from '../../../ui/components/types';
@@ -145,7 +145,7 @@ class MultiSelectAutocomplete extends Component<IProps, IState> {
this._onFilterChange = this._onFilterChange.bind(this);
this._onRetry = this._onRetry.bind(this);
this._onSelectionChange = this._onSelectionChange.bind(this);
this._sendQuery = _debounce(this._sendQuery.bind(this), 200);
this._sendQuery = debounce(this._sendQuery.bind(this), 200);
}
/**

View File

@@ -5,6 +5,7 @@ export interface IIconButtonProps {
accessibilityLabel?: string;
color?: string;
disabled?: boolean;
id?: string;
onPress?: (e?: GestureResponderEvent) => void;
size?: number | string;
src: Function;

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { isEqual } from 'lodash-es';
import { IReduxState, IStore } from '../../app/types';
import { IStateful } from '../app/types';
@@ -36,7 +36,7 @@ export function assign<T extends Object>(target: T, source: Partial<T>): T {
* comparison); false, otherwise.
*/
export function equals(a: any, b: any) {
return _.isEqual(a, b);
return isEqual(a, b);
}
/**

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { throttle } from 'lodash-es';
import MiddlewareRegistry from './MiddlewareRegistry';
import PersistenceRegistry from './PersistenceRegistry';
@@ -13,10 +13,7 @@ const PERSIST_STATE_DELAY = 2000;
/**
* A throttled function to avoid repetitive state persisting.
*/
const throttledPersistState
= _.throttle(
state => PersistenceRegistry.persistState(state),
PERSIST_STATE_DELAY);
const throttledPersistState = throttle(state => PersistenceRegistry.persistState(state), PERSIST_STATE_DELAY);
// Web only code.
// We need the <tt>if</tt> because it appears that on mobile the polyfill is not

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { escape } from 'lodash-es';
import { AnyAction } from 'redux';
import { IStore } from '../../app/types';
@@ -100,8 +100,8 @@ function _updateLocalParticipantFromUrl({ dispatch, getState }: IStore) {
const localParticipant = getLocalParticipant(getState());
if (localParticipant) {
const displayName = _.escape(urlDisplayName);
const email = _.escape(urlEmail);
const displayName = escape(urlDisplayName);
const email = escape(urlEmail);
dispatch(participantUpdated({
...localParticipant,

View File

@@ -1,6 +1,6 @@
// @ts-expect-error
import { jitsiLocalStorage } from '@jitsi/js-utils';
import _ from 'lodash';
import { escape } from 'lodash-es';
import { APP_WILL_MOUNT } from '../app/actionTypes';
import PersistenceRegistry from '../redux/PersistenceRegistry';
@@ -154,8 +154,8 @@ function _initSettings(featureState: ISettingsState) {
// is a defined value, it will override any value found in local storage.
// The workaround is sidestepping _.escape when the value is not set in
// local storage.
const displayName = savedDisplayName === null ? undefined : _.escape(savedDisplayName);
const email = savedEmail === null ? undefined : _.escape(savedEmail);
const displayName = savedDisplayName === null ? undefined : escape(savedDisplayName);
const email = savedEmail === null ? undefined : escape(savedEmail);
settings = assignIfDefined({
displayName,

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { isEqual, sortBy } from 'lodash-es';
import { MEDIA_TYPE } from '../media/constants';
import { getScreenshareParticipantIds } from '../participants/functions';
@@ -16,7 +16,7 @@ StateListenerRegistry.register(
return;
}
if (!_.isEqual(_.sortBy(participantIDs), _.sortBy(previousParticipantIDs))) {
if (!isEqual(sortBy(participantIDs), sortBy(previousParticipantIDs))) {
APP.API.notifySharingParticipantsChanged(participantIDs);
}
}

View File

@@ -1,7 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { TouchableHighlight } from 'react-native';
import { StyleProp, TouchableHighlight } from 'react-native';
import { Button as NativePaperButton, Text } from 'react-native-paper';
import { IconSource } from 'react-native-paper/lib/typescript/components/Icon';
import { BUTTON_MODES, BUTTON_TYPES } from '../../constants.native';
import BaseTheme from '../BaseTheme.native';
@@ -13,6 +14,7 @@ import styles from './buttonStyles';
export interface IProps extends IButtonProps {
color?: string | undefined;
contentStyle?: Object | undefined;
id?: string;
labelStyle?: Object | undefined;
mode?: any;
style?: Object | undefined;
@@ -24,6 +26,7 @@ const Button: React.FC<IProps> = ({
contentStyle,
disabled,
icon,
id,
labelKey,
labelStyle,
mode = BUTTON_MODES.CONTAINED,
@@ -74,16 +77,17 @@ const Button: React.FC<IProps> = ({
<TouchableHighlight
accessibilityLabel = { accessibilityLabel }
disabled = { disabled }
id = { id }
onPress = { onPress }
style = { [
buttonStyles,
style
] }>
] as StyleProp<object> }>
<Text
style = { [
buttonLabelStyles,
labelStyle
] }>{ t(labelKey ?? '') }</Text>
] as StyleProp<object> }>{ t(labelKey ?? '') }</Text>
</TouchableHighlight>
);
}
@@ -96,21 +100,20 @@ const Button: React.FC<IProps> = ({
contentStyle = { [
styles.buttonContent,
contentStyle
] }
] as StyleProp<object> }
disabled = { disabled }
// @ts-ignore
icon = { icon }
icon = { icon as IconSource | undefined }
id = { id }
labelStyle = { [
buttonLabelStyles,
labelStyle
] }
] as StyleProp<object> }
mode = { mode }
onPress = { onPress }
style = { [
buttonStyles,
style
] } />
] as StyleProp<object> } />
);
};

View File

@@ -12,6 +12,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
accessibilityLabel,
color: iconColor,
disabled,
id,
onPress,
size,
src,
@@ -52,6 +53,7 @@ const IconButton: React.FC<IIconButtonProps> = ({
<TouchableHighlight
accessibilityLabel = { accessibilityLabel }
disabled = { disabled }
id = { id }
onPress = { onPress }
style = { [
iconButtonContainerStyles,

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { ColorValue } from 'react-native';
import { ColorValue, StyleProp } from 'react-native';
import { Switch as NativeSwitch } from 'react-native-paper';
import { ISwitchProps } from '../types';
@@ -12,6 +12,12 @@ import {
interface IProps extends ISwitchProps {
/**
* Id for the switch.
*/
id?: string;
/**
* Custom styles for the switch.
*/
@@ -31,6 +37,7 @@ interface IProps extends ISwitchProps {
const Switch = ({
checked,
disabled,
id,
onChange,
thumbColor = THUMB_COLOR,
trackColor = {
@@ -41,9 +48,10 @@ const Switch = ({
}: IProps) => (
<NativeSwitch
disabled = { disabled }
id = { id }
ios_backgroundColor = { DISABLED_TRACK_COLOR }
onValueChange = { onChange }
style = { style }
style = { style as StyleProp<object> }
thumbColor = { thumbColor }
trackColor = { trackColor }
value = { checked } />

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { isEqual } from 'lodash-es';
import { NIL, parse as parseUUID } from 'uuid';
import zxcvbn from 'zxcvbn';
@@ -22,7 +22,7 @@ function isValidUUID(str: string) {
return false;
}
return !_.isEqual(uuid, NIL_UUID);
return !isEqual(uuid, NIL_UUID);
}
/**

View File

@@ -1,5 +1,5 @@
import i18next from 'i18next';
import _ from 'lodash';
import { chunk, filter, shuffle } from 'lodash-es';
import { createBreakoutRoomsEvent } from '../analytics/AnalyticsEvents';
import { sendAnalytics } from '../analytics/functions';
@@ -129,14 +129,14 @@ export function removeBreakoutRoom(breakoutRoomJid: string) {
export function autoAssignToBreakoutRooms() {
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
const rooms = getBreakoutRooms(getState);
const breakoutRooms = _.filter(rooms, room => !room.isMainRoom);
const breakoutRooms = filter(rooms, room => !room.isMainRoom);
if (breakoutRooms) {
sendAnalytics(createBreakoutRoomsEvent('auto.assign'));
const participantIds = Array.from(getRemoteParticipants(getState).keys());
const length = Math.ceil(participantIds.length / breakoutRooms.length);
_.chunk(_.shuffle(participantIds), length).forEach((group, index) =>
chunk(shuffle(participantIds), length).forEach((group, index) =>
group.forEach(participantId => {
dispatch(sendParticipantToRoom(participantId, breakoutRooms[index].id));
})

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { find } from 'lodash-es';
import { IStateful } from '../base/app/types';
import { getCurrentConference } from '../base/conference/functions';
@@ -33,7 +33,7 @@ export const getBreakoutRooms = (stateful: IStateful): IRooms => toState(statefu
export const getMainRoom = (stateful: IStateful) => {
const rooms = getBreakoutRooms(stateful);
return _.find(rooms, room => Boolean(room.isMainRoom));
return find(rooms, room => Boolean(room.isMainRoom));
};
/**
@@ -135,7 +135,7 @@ export const getRoomsInfo = (stateful: IStateful) => {
export const getRoomByJid = (stateful: IStateful, roomJid: string) => {
const rooms = getBreakoutRooms(stateful);
return _.find(rooms, (room: IRoom) => room.jid === roomJid);
return find(rooms, (room: IRoom) => room.jid === roomJid);
};
/**

View File

@@ -83,12 +83,12 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const { messages } = getState()['features/chat'];
messages?.forEach(m => {
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.id)) {
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.participantId)) {
const rooms: IRooms = action.rooms;
for (const room of Object.values(rooms)) {
const participants = room.participants || {};
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.id));
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.participantId));
if (matchedJid) {
m.displayName = participants[matchedJid].displayName;

View File

@@ -36,13 +36,13 @@ export default class AbstractMessageContainer<P extends IProps, S> extends Compo
for (let i = 0; i < messagesCount; i++) {
const message = this.props.messages[i];
if (message.id === currentGroupParticipantId) {
if (message.participantId === currentGroupParticipantId) {
currentGrouping.push(message);
} else {
currentGrouping.length && groups.push(currentGrouping);
currentGrouping = [ message ];
currentGroupParticipantId = message.id;
currentGroupParticipantId = message.participantId;
}
}

View File

@@ -84,6 +84,7 @@ class ChatInputBar extends Component<IProps, IState> {
return (
<View
id = 'chat-input'
style = { [
inputBarStyles,
this.state.addPadding ? styles.extraBarPadding : null
@@ -91,6 +92,7 @@ class ChatInputBar extends Component<IProps, IState> {
<Input
blurOnSubmit = { false }
customStyles = {{ container: styles.customInputContainer }}
id = 'chat-input-messagebox'
multiline = { false }
onBlur = { this._onFocused(false) }
onChange = { this._onChangeText }
@@ -101,6 +103,7 @@ class ChatInputBar extends Component<IProps, IState> {
value = { this.state.message } />
<IconButton
disabled = { !this.state.message }
id = { this.props.t('chat.sendButton') }
onPress = { this._onSubmit }
src = { IconSend }
type = { BUTTON_TYPES.PRIMARY } />

View File

@@ -6,8 +6,8 @@ import { IReduxState } from '../../../app/types';
import Avatar from '../../../base/avatar/components/Avatar';
import { translate } from '../../../base/i18n/functions';
import Linkify from '../../../base/react/components/native/Linkify';
import { isGifMessage } from '../../../gifs/functions.native';
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import { isGifEnabled, isGifMessage } from '../../../gifs/functions.native';
import { CHAR_LIMIT, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
import {
getCanReplyToMessage,
getFormattedTimestamp,
@@ -32,7 +32,7 @@ class ChatMessage extends Component<IChatMessageProps> {
* @inheritdoc
*/
render() {
const { message, knocking } = this.props;
const { gifEnabled, message, knocking } = this.props;
const localMessage = message.messageType === MESSAGE_TYPE_LOCAL;
const { privateMessage, lobbyChat } = message;
@@ -73,24 +73,20 @@ class ChatMessage extends Component<IChatMessageProps> {
messageBubbleStyle.push(styles.lobbyMessageBubble);
}
const messageText = replaceNonUnicodeEmojis(getMessageText(this.props.message));
const messageText = getMessageText(this.props.message);
return (
<View style = { styles.messageWrapper as ViewStyle } >
<View
id = { message.messageId }
style = { styles.messageWrapper as ViewStyle } >
{ this._renderAvatar() }
<View style = { detailsWrapperStyle }>
<View style = { messageBubbleStyle }>
<View style = { styles.textWrapper as ViewStyle } >
{ this._renderDisplayName() }
{ isGifMessage(messageText)
{ gifEnabled && isGifMessage(messageText)
? <GifMessage message = { messageText } />
: (
<Linkify
linkStyle = { styles.chatLink }
style = { styles.chatMessage }>
{ messageText }
</Linkify>
)}
: this._renderMessageTextComponent(messageText) }
{ this._renderPrivateNotice() }
</View>
{ this._renderPrivateReplyButton() }
@@ -104,7 +100,7 @@ class ChatMessage extends Component<IChatMessageProps> {
/**
* Renders the avatar of the sender.
*
* @returns {React$Element<*>}
* @returns {React.ReactElement<*>}
*/
_renderAvatar() {
const { message } = this.props;
@@ -113,7 +109,7 @@ class ChatMessage extends Component<IChatMessageProps> {
<View style = { styles.avatarWrapper }>
{ this.props.showAvatar && <Avatar
displayName = { message.displayName }
participantId = { message.id }
participantId = { message.participantId }
size = { styles.avatarWrapper.width } />
}
</View>
@@ -123,7 +119,7 @@ class ChatMessage extends Component<IChatMessageProps> {
/**
* Renders the display name of the sender if necessary.
*
* @returns {React$Element<*> | null}
* @returns {React.ReactElement<*> | null}
*/
_renderDisplayName() {
const { message, showDisplayName } = this.props;
@@ -139,10 +135,37 @@ class ChatMessage extends Component<IChatMessageProps> {
);
}
/**
* Renders the message text based on number of characters.
*
* @param {string} messageText - The message text.
* @returns {React.ReactElement<*>}
*/
_renderMessageTextComponent(messageText: string) {
if (messageText.length >= CHAR_LIMIT) {
return (
<Text
selectable = { true }
style = { styles.chatMessage }>
{ messageText }
</Text>
);
}
return (
<Linkify
linkStyle = { styles.chatLink }
style = { styles.chatMessage }>
{ replaceNonUnicodeEmojis(messageText) }
</Linkify>
);
}
/**
* Renders the message privacy notice, if necessary.
*
* @returns {React$Element<*> | null}
* @returns {React.ReactElement<*> | null}
*/
_renderPrivateNotice() {
const { message, knocking } = this.props;
@@ -161,7 +184,7 @@ class ChatMessage extends Component<IChatMessageProps> {
/**
* Renders the private reply button, if necessary.
*
* @returns {React$Element<*> | null}
* @returns {React.ReactElement<*> | null}
*/
_renderPrivateReplyButton() {
const { message, canReply } = this.props;
@@ -175,7 +198,7 @@ class ChatMessage extends Component<IChatMessageProps> {
<View style = { styles.replyContainer as ViewStyle }>
<PrivateMessageButton
isLobbyMessage = { lobbyChat }
participantID = { message.id }
participantID = { message.participantId }
reply = { true }
showLabel = { false }
toggledStyles = { styles.replyStyles } />
@@ -186,7 +209,7 @@ class ChatMessage extends Component<IChatMessageProps> {
/**
* Renders the time at which the message was sent, if necessary.
*
* @returns {React$Element<*> | null}
* @returns {React.ReactElement<*> | null}
*/
_renderTimestamp() {
if (!this.props.showTimestamp) {
@@ -205,11 +228,13 @@ class ChatMessage extends Component<IChatMessageProps> {
* Maps part of the redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {IChatMessageProps} message - Message object.
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState, { message }: IChatMessageProps) {
return {
canReply: getCanReplyToMessage(state, message),
gifEnabled: isGifEnabled(state),
knocking: state['features/lobby'].knocking
};
}

View File

@@ -17,6 +17,7 @@ const GifMessage = ({ message }: IProps) => {
const url = message.substring(GIF_PREFIX.length, message.length - 1);
return (<View
id = 'gif-message'
style = { styles.gifContainer }>
<Image
source = {{ uri: url }}

View File

@@ -78,7 +78,9 @@ class MessageContainer extends AbstractMessageContainer<IProps, any> {
const { t } = this.props;
return (
<View style = { styles.emptyComponentWrapper as ViewStyle }>
<View
id = 'no-messages-message'
style = { styles.emptyComponentWrapper as ViewStyle }>
<Text style = { styles.emptyComponentText as TextStyle }>
{ t('chat.noMessagesMessage') }
</Text>

View File

@@ -102,7 +102,9 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
if (isLobbyChatActive) {
return (
<View style = { styles.lobbyMessageRecipientContainer as ViewStyle }>
<View
id = 'chat-recipient'
style = { styles.lobbyMessageRecipientContainer as ViewStyle }>
<Text style = { styles.messageRecipientText }>
{ t('chat.lobbyChatMessageTo', {
recipient: lobbyMessageRecipient?.name
@@ -123,13 +125,16 @@ class MessageRecipient extends AbstractMessageRecipient<IProps> {
}
return (
<View style = { styles.messageRecipientContainer as ViewStyle }>
<View
id = 'message-recipient'
style = { styles.messageRecipientContainer as ViewStyle }>
<Text style = { styles.messageRecipientText }>
{ t('chat.messageTo', {
recipient: privateMessageRecipient.name
}) }
</Text>
<TouchableHighlight
id = 'message-recipient-cancel-button'
onPress = { this._onResetPrivateMessageRecipient }
underlayColor = { 'transparent' }>
<Icon

View File

@@ -197,7 +197,7 @@ const ChatMessage = ({
className = { classes.replyButtonContainer }>
<PrivateMessageButton
isLobbyMessage = { message.lobbyChat }
participantID = { message.id } />
participantID = { message.participantId } />
</div>
)}
</div>

View File

@@ -66,7 +66,7 @@ const ChatMessageGroup = ({ className = '', messages }: IProps) => {
<div className = { clsx(classes.groupContainer, className) }>
<Avatar
className = { clsx(classes.avatar, 'avatar') }
participantId = { messages[0].id }
participantId = { messages[0].participantId }
size = { 32 } />
<div className = { `${classes.messageGroup} chat-message-group ${className}` }>
{messages.map((message, i) => (

View File

@@ -1,4 +1,4 @@
import throttle from 'lodash/throttle';
import { throttle } from 'lodash-es';
import React, { RefObject } from 'react';
import { scrollIntoView } from 'seamless-scroll-polyfill';

View File

@@ -1,3 +1,8 @@
/**
* Maximum number of characters allowed.
*/
export const CHAR_LIMIT = 500;
/**
* The size of the chat. Equal to $sidebarWidth SCSS variable.
*/

View File

@@ -172,7 +172,7 @@ export function getMessageText(message: IMessage) {
*/
export function getCanReplyToMessage(state: IReduxState, message: IMessage) {
const { knocking } = state['features/lobby'];
const participant = getParticipantById(state, message.id);
const participant = getParticipantById(state, message.participantId);
return Boolean(participant)
&& (message.privateMessage || (message.lobbyChat && !knocking))

View File

@@ -25,7 +25,7 @@ import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { playSound, registerSound, unregisterSound } from '../base/sounds/actions';
import { addGif } from '../gifs/actions';
import { GIF_PREFIX } from '../gifs/constants';
import { getGifDisplayMode, isGifMessage } from '../gifs/function.any';
import { getGifDisplayMode, isGifEnabled, isGifMessage } from '../gifs/function.any';
import { showMessageNotification } from '../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
import { resetNbUnreadPollsMessages } from '../polls/actions';
@@ -125,7 +125,7 @@ MiddlewareRegistry.register(store => next => action => {
store.dispatch(pushReactions(data.reactions));
_handleReceivedMessage(store, {
id: participant.getId(),
participantId: participant.getId(),
message: getReactionMessageFromBuffer(data.reactions),
privateMessage: false,
lobbyChat: false,
@@ -137,12 +137,12 @@ MiddlewareRegistry.register(store => next => action => {
}
case NON_PARTICIPANT_MESSAGE_RECEIVED: {
const { id, json: data } = action;
const { participantId, json: data } = action;
if (data?.type === MESSAGE_TYPE_SYSTEM && data.message) {
_handleReceivedMessage(store, {
displayName: data.displayName ?? i18next.t('chat.systemDisplayName'),
id,
participantId,
lobbyChat: false,
message: data.message,
privateMessage: true,
@@ -213,7 +213,7 @@ MiddlewareRegistry.register(store => next => action => {
case ADD_REACTION_MESSAGE: {
if (localParticipant?.id) {
_handleReceivedMessage(store, {
id: localParticipant.id,
participantId: localParticipant.id,
message: action.message,
privateMessage: false,
timestamp: Date.now(),
@@ -274,25 +274,30 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
conference.on(
JitsiConferenceEvents.MESSAGE_RECEIVED,
// eslint-disable-next-line max-params
(id: string, message: string, timestamp: number, displayName: string, isGuest?: boolean) => {
/* eslint-disable max-params */
(participantId: string, message: string, timestamp: number,
displayName: string, isGuest: boolean, messageId: string) => {
/* eslint-enable max-params */
_onConferenceMessageReceived(store, {
id: id || displayName, // in case of messages coming from visitors we can have unknown id
// in case of messages coming from visitors we can have unknown id
participantId: participantId || displayName,
message,
timestamp,
displayName,
isGuest,
messageId,
privateMessage: false });
}
);
conference.on(
JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED,
(id: string, message: string, timestamp: number) => {
(participantId: string, message: string, timestamp: number, messageId: string) => {
_onConferenceMessageReceived(store, {
id,
participantId,
message,
timestamp,
messageId,
privateMessage: true
});
}
@@ -311,25 +316,29 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
* @param {Object} message - The message object.
* @returns {void}
*/
function _onConferenceMessageReceived(store: IStore, { displayName, id, isGuest, message, timestamp, privateMessage }: {
displayName?: string; id: string; isGuest?: boolean;
message: string; privateMessage: boolean; timestamp: number; }) {
const isGif = isGifMessage(message);
function _onConferenceMessageReceived(store: IStore,
{ displayName, isGuest, message, messageId, participantId, privateMessage, timestamp }: {
displayName?: string; isGuest?: boolean; message: string; messageId?: string;
participantId: string; privateMessage: boolean; timestamp: number; }
) {
const isGif = isGifEnabled(store.getState()) && isGifMessage(message);
if (isGif) {
_handleGifMessageReceived(store, id, message);
_handleGifMessageReceived(store, participantId, message);
if (getGifDisplayMode(store.getState()) === 'tile') {
return;
}
}
_handleReceivedMessage(store, {
displayName,
id,
isGuest,
participantId,
message,
privateMessage,
lobbyChat: false,
timestamp
timestamp,
messageId
}, true, isGif);
}
@@ -337,14 +346,14 @@ function _onConferenceMessageReceived(store: IStore, { displayName, id, isGuest,
* Handles a received gif message.
*
* @param {Object} store - Redux store.
* @param {string} id - Id of the participant that sent the message.
* @param {string} participantId - Id of the participant that sent the message.
* @param {string} message - The message sent.
* @returns {void}
*/
function _handleGifMessageReceived(store: IStore, id: string, message: string) {
function _handleGifMessageReceived(store: IStore, participantId: string, message: string) {
const url = message.substring(GIF_PREFIX.length, message.length - 1);
store.dispatch(addGif(id, url));
store.dispatch(addGif(participantId, url));
}
/**
@@ -374,7 +383,7 @@ function _handleChatError({ dispatch }: IStore, error: Error) {
export function handleLobbyMessageReceived(message: string, participantId: string) {
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
_handleReceivedMessage({ dispatch,
getState }, { id: participantId,
getState }, { participantId,
message,
privateMessage: false,
lobbyChat: true,
@@ -387,18 +396,18 @@ export function handleLobbyMessageReceived(message: string, participantId: strin
* Function to get lobby chat user display name.
*
* @param {Store} state - The Redux store.
* @param {string} id - The knocking participant id.
* @param {string} participantId - The knocking participant id.
* @returns {string}
*/
function getLobbyChatDisplayName(state: IReduxState, id: string) {
function getLobbyChatDisplayName(state: IReduxState, participantId: string) {
const { knockingParticipants } = state['features/lobby'];
const { lobbyMessageRecipient } = state['features/chat'];
if (id === lobbyMessageRecipient?.id) {
if (participantId === lobbyMessageRecipient?.id) {
return lobbyMessageRecipient.name;
}
const knockingParticipant = knockingParticipants.find(p => p.id === id);
const knockingParticipant = knockingParticipants.find(p => p.id === participantId);
if (knockingParticipant) {
return knockingParticipant.name;
@@ -417,9 +426,9 @@ function getLobbyChatDisplayName(state: IReduxState, id: string) {
* @returns {void}
*/
function _handleReceivedMessage({ dispatch, getState }: IStore,
{ displayName, id, isGuest, message, privateMessage, timestamp, lobbyChat }: {
displayName?: string; id: string; isGuest?: boolean; lobbyChat: boolean;
message: string; privateMessage: boolean; timestamp: number; },
{ displayName, isGuest, lobbyChat, message, messageId, participantId, privateMessage, timestamp }: {
displayName?: string; isGuest?: boolean; lobbyChat: boolean; message: string;
messageId?: string; participantId: string; privateMessage: boolean; timestamp: number; },
shouldPlaySound = true,
isReaction = false
) {
@@ -434,12 +443,12 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
// Provide a default for the case when a message is being
// backfilled for a participant that has left the conference.
const participant = getParticipantById(state, id) || { local: undefined };
const participant = getParticipantById(state, participantId) || { local: undefined };
const localParticipant = getLocalParticipant(getState);
let displayNameToShow = lobbyChat
? getLobbyChatDisplayName(state, id)
: displayName || getParticipantDisplayName(state, id);
? getLobbyChatDisplayName(state, participantId)
: displayName || getParticipantDisplayName(state, participantId);
const hasRead = participant.local || isChatOpen;
const timestampToDate = timestamp ? new Date(timestamp) : new Date();
const millisecondsTimestamp = timestampToDate.getTime();
@@ -455,13 +464,14 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
dispatch(addMessage({
displayName: displayNameToShow,
hasRead,
id,
participantId,
messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
message,
privateMessage,
lobbyChat,
recipient: getParticipantDisplayName(state, localParticipant?.id ?? ''),
timestamp: millisecondsTimestamp,
messageId,
isReaction
}));
@@ -477,7 +487,7 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
APP.API.notifyReceivedChatMessage({
body: message,
id,
from: participantId,
nick: displayNameToShow,
privateMessage,
ts: timestamp
@@ -512,7 +522,7 @@ function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipientID:
dispatch(addMessage({
displayName,
hasRead: true,
id: localParticipant.id,
participantId: localParticipant.id,
messageType: MESSAGE_TYPE_LOCAL,
message,
privateMessage: !isLobbyPrivateMessage,
@@ -562,7 +572,7 @@ function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
if (lastMessage.privateMessage) {
// We show the notice if the last received message was private.
return lastMessage.id;
return lastMessage.participantId;
}
// But messages may come rapidly, we want to protect our users from mis-sending a message
@@ -577,7 +587,7 @@ function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
? recentPrivateMessages[0] : recentPrivateMessages[recentPrivateMessages.length - 1];
if (recentPrivateMessage) {
return recentPrivateMessage.id;
return recentPrivateMessage.participantId;
}
return undefined;

View File

@@ -1,5 +1,3 @@
import { v4 as uuidv4 } from 'uuid';
import { ILocalParticipant, IParticipant } from '../base/participants/types';
import ReducerRegistry from '../base/redux/ReducerRegistry';
@@ -48,9 +46,9 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
const newMessage: IMessage = {
displayName: action.displayName,
error: action.error,
id: action.id,
participantId: action.participantId,
isReaction: action.isReaction,
messageId: uuidv4(),
messageId: action.messageId,
messageType: action.messageType,
message: action.message,
privateMessage: action.privateMessage,

View File

@@ -5,12 +5,12 @@ import { IStore } from '../app/types';
export interface IMessage {
displayName: string;
error?: Object;
id: string;
isReaction: boolean;
lobbyChat: boolean;
message: string;
messageId: string;
messageType: string;
participantId: string;
privateMessage: boolean;
recipient: string;
timestamp: number;
@@ -44,6 +44,11 @@ export interface IChatMessageProps extends WithTranslation {
*/
canReply?: boolean;
/**
* Whether gifs are enabled or not.
*/
gifEnabled?: boolean;
/**
* Whether current participant is currently knocking in the lobby room.
*/

View File

@@ -15,7 +15,7 @@ import { connect, useDispatch } from 'react-redux';
import { appNavigate } from '../../../app/actions.native';
import { IReduxState, IStore } from '../../../app/types';
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
import { FULLSCREEN_ENABLED } from '../../../base/flags/constants';
import { getFeatureFlag } from '../../../base/flags/functions';
import Container from '../../../base/react/components/native/Container';
import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
@@ -38,7 +38,7 @@ import LargeVideo from '../../../large-video/components/LargeVideo.native';
import { getIsLobbyVisible } from '../../../lobby/functions';
import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { screen } from '../../../mobile/navigation/routes';
import { setPictureInPictureEnabled } from '../../../mobile/picture-in-picture/functions';
import { isPipEnabled, setPictureInPictureEnabled } from '../../../mobile/picture-in-picture/functions';
import Captions from '../../../subtitles/components/native/Captions';
import { setToolboxVisible } from '../../../toolbox/actions.native';
import Toolbox from '../../../toolbox/components/native/Toolbox';
@@ -579,7 +579,7 @@ function _mapStateToProps(state: IReduxState, _ownProps: any) {
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
_isParticipantsPaneOpen: isOpen,
_largeVideoParticipantId: state['features/large-video'].participantId,
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
_pictureInPictureEnabled: isPipEnabled(state),
_reducedUI: reducedUI,
_showLobby: getIsLobbyVisible(state),
_startCarMode: startCarMode,

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { throttle } from 'lodash-es';
import React from 'react';
import { WithTranslation } from 'react-i18next';
import { connect as reactReduxConnect } from 'react-redux';
@@ -133,7 +133,7 @@ class Conference extends AbstractConference<IProps, any> {
this._originalOnShowToolbar = this._onShowToolbar;
this._originalOnMouseMove = this._onMouseMove;
this._onShowToolbar = _.throttle(
this._onShowToolbar = throttle(
() => this._originalOnShowToolbar(),
100,
{
@@ -141,7 +141,7 @@ class Conference extends AbstractConference<IProps, any> {
trailing: false
});
this._onMouseMove = _.throttle(
this._onMouseMove = throttle(
event => this._originalOnMouseMove(event),
_mouseMoveCallbackInterval,
{

View File

@@ -13,6 +13,7 @@ import {
ENDPOINT_MESSAGE_RECEIVED
} from '../base/conference/actionTypes';
import { getCurrentConference } from '../base/conference/functions';
import { getDisableLowerHandByModerator } from '../base/config/functions.any';
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
import { hideDialog } from '../base/dialog/actions';
import { isDialogOpen } from '../base/dialog/functions';
@@ -75,9 +76,11 @@ MiddlewareRegistry.register(store => next => action => {
}
case ENDPOINT_MESSAGE_RECEIVED: {
const { participant, data } = action;
const { dispatch } = store;
const { dispatch, getState } = store;
if (data.name === LOWER_HAND_MESSAGE && participant.isModerator()) {
if (data.name === LOWER_HAND_MESSAGE
&& participant.isModerator()
&& !getDisableLowerHandByModerator(getState())) {
dispatch(raiseHand(false));
}
break;

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { union } from 'lodash-es';
import { IJitsiConference } from '../base/conference/reducer';
import {
@@ -143,7 +143,7 @@ const statsEmitter = {
const resolutionUserIds = Object.keys(allUserResolutions);
const codecUserIds = Object.keys(allUserCodecs);
_.union(framerateUserIds, resolutionUserIds, codecUserIds)
union(framerateUserIds, resolutionUserIds, codecUserIds)
.filter(id => id !== localUserId)
.forEach(id => {
const remoteUserStats: IStats = {};

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx';
import _ from 'lodash';
import { throttle } from 'lodash-es';
import React, { PureComponent } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';
@@ -286,7 +286,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
this._onDragMouseUp = this._onDragMouseUp.bind(this);
this._onFilmstripResize = this._onFilmstripResize.bind(this);
this._throttledResize = _.throttle(
this._throttledResize = throttle(
this._onFilmstripResize,
50,
{

View File

@@ -1,6 +1,6 @@
import { Theme } from '@mui/material';
import clsx from 'clsx';
import debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
import React, { Component, KeyboardEvent, RefObject, createRef } from 'react';
import { WithTranslation } from 'react-i18next';
import { connect } from 'react-redux';

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { differenceWith, isEqual } from 'lodash-es';
import { IStore } from '../app/types';
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
@@ -184,9 +184,9 @@ function _onFollowMeCommand(attributes: any = {}, id: string, store: IStore) {
oldStageParticipants = JSON.parse(oldState.pinnedStageParticipants);
}
if (!_.isEqual(stageParticipants, oldStageParticipants)) {
const toRemove = _.differenceWith(oldStageParticipants, stageParticipants, _.isEqual);
const toAdd = _.differenceWith(stageParticipants, oldStageParticipants, _.isEqual);
if (!isEqual(stageParticipants, oldStageParticipants)) {
const toRemove = differenceWith(oldStageParticipants, stageParticipants, isEqual);
const toAdd = differenceWith(stageParticipants, oldStageParticipants, isEqual);
toRemove.forEach((p: { participantId: string; }) =>
store.dispatch(removeStageParticipant(p.participantId)));

View File

@@ -9,7 +9,7 @@ import JitsiScreen from '../../../base/modal/components/JitsiScreen';
import Input from '../../../base/ui/components/native/Input';
import { sendMessage } from '../../../chat/actions.any';
import { goBack } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
import { formatGifUrlMessage, getGifRating, getGifUrl, getGiphyProxyUrl } from '../../functions.native';
import { formatGifUrlMessage, getGifRating, getGifUrl } from '../../functions.native';
import GifsMenuFooter from './GifsMenuFooter';
import styles from './styles';
@@ -19,8 +19,6 @@ const GifsMenu = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const rating = useSelector(getGifRating) as GiphyRating;
const proxyUrl = useSelector(getGiphyProxyUrl);
const options = {
mediaType: GiphyMediaType.Gif,
limit: 20,
@@ -35,7 +33,7 @@ const GifsMenu = () => {
});
const sendGif = useCallback(e => {
const url = getGifUrl(e.nativeEvent.media, proxyUrl);
const url = getGifUrl(e.nativeEvent.media);
sendAnalytics(createGifSentEvent());

View File

@@ -1,4 +1,4 @@
import { GiphyFetch, TrendingOptions, setServerUrl } from '@giphy/js-fetch-api';
import { GiphyFetch, TrendingOptions } from '@giphy/js-fetch-api';
import { Grid } from '@giphy/react-components';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
@@ -20,8 +20,7 @@ import {
formatGifUrlMessage,
getGifAPIKey,
getGifRating,
getGifUrl,
getGiphyProxyUrl
getGifUrl
} from '../../function.any';
const OVERFLOW_DRAWER_PADDING = 16;
@@ -104,7 +103,6 @@ function GifsMenu({ columns = 2, parent }: IProps) {
= parent === IReactionsMenuParent.OverflowDrawer || parent === IReactionsMenuParent.OverflowMenu;
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
const rating = useSelector(getGifRating);
const proxyUrl = useSelector(getGiphyProxyUrl);
const fetchGifs = useCallback(async (offset = 0) => {
const options: TrendingOptions = {
@@ -126,7 +124,7 @@ function GifsMenu({ columns = 2, parent }: IProps) {
const handleGifClick = useCallback((gif, e) => {
e?.stopPropagation();
const url = getGifUrl(gif, proxyUrl);
const url = getGifUrl(gif);
sendAnalytics(createGifSentEvent());
batch(() => {
@@ -189,12 +187,6 @@ function GifsMenu({ columns = 2, parent }: IProps) {
// This fixes that.
useEffect(() => setSearchKey(''), []);
useEffect(() => {
if (proxyUrl) {
setServerUrl(proxyUrl);
}
}, []);
const onInputKeyPress = useCallback((e: React.KeyboardEvent) => {
e.stopPropagation();
}, []);

View File

@@ -33,15 +33,6 @@ export function getGifRating(state: IReduxState) {
return getGifConfig(state).rating || GIF_DEFAULT_RATING;
}
/**
* Get the Giphy proxy url.
*
* @param {IReduxState} state - Redux state.
* @returns {string}
*/
export function getGiphyProxyUrl(state: IReduxState) {
return getGifConfig(state).proxyUrl;
}
/**
* Gets the URL of the GIF for the given participant or null if there's none.
@@ -54,6 +45,26 @@ export function getGifForParticipant(state: IReduxState, participantId: string):
return isGifEnabled(state) ? state['features/gifs'].gifList.get(participantId) || {} : {};
}
/**
* Returns true if a given URL is allowed to be rendered as gif and false otherwise.
*
* @param {string} url - The URL to be validated.
* @returns {boolean} - True if a given URL is allowed to be rendered as gif and false otherwise.
*/
export function isGifUrlAllowed(url: string) {
let hostname: string | undefined;
try {
const urlObject = new URL(url);
hostname = urlObject?.hostname;
} catch (_error) {
return false;
}
return hostname === 'i.giphy.com';
}
/**
* Whether or not the message is a GIF message.
*
@@ -61,26 +72,23 @@ export function getGifForParticipant(state: IReduxState, participantId: string):
* @returns {boolean}
*/
export function isGifMessage(message: string) {
const url = message.substring(GIF_PREFIX.length, message.length - 1);
return message.trim().toLowerCase()
.startsWith(GIF_PREFIX);
.startsWith(GIF_PREFIX) && isGifUrlAllowed(url);
}
/**
* Returns the url of the gif selected in the gifs menu.
*
* @param {Object} gif - The gif data.
* @param {string} proxyUrl - The proxy server url.
* @returns {boolean}
*/
export function getGifUrl(gif?: { data?: { embed_url: string; }; embed_url?: string; }, proxyUrl?: string) {
export function getGifUrl(gif?: { data?: { embed_url: string; }; embed_url?: string; }) {
const embedUrl = gif?.embed_url || gif?.data?.embed_url || '';
const idx = embedUrl.lastIndexOf('/');
const id = embedUrl.substr(idx + 1);
if (proxyUrl) {
return `${proxyUrl}gifs/id/${id}`;
}
return `https://i.giphy.com/media/${id}/giphy.gif`;
}

View File

@@ -82,14 +82,19 @@ export function invite(
const { conference, password } = state['features/base/conference'];
if (typeof conference === 'undefined') {
// Only keep invitees which can get an invite request from Jitsi UI
const jitsiInvitees = invitees.filter(({ type }) => type !== INVITE_TYPES.EMAIL);
// Invite will fail before CONFERENCE_JOIN. The request will be
// cached in order to be executed on CONFERENCE_JOIN.
return new Promise(resolve => {
dispatch(addPendingInviteRequest({
invitees,
callback: (failedInvitees: any) => resolve(failedInvitees)
}));
});
if (jitsiInvitees.length) {
return new Promise(resolve => {
dispatch(addPendingInviteRequest({
invitees: jitsiInvitees,
callback: (failedInvitees: any) => resolve(failedInvitees)
}));
});
}
}
let allInvitePromises: Promise<any>[] = [];
@@ -112,10 +117,12 @@ export function invite(
// For each number, dial out. On success, remove the number from
// {@link invitesLeftToSend}.
const phoneInvitePromises = phoneNumbers.map(item => {
const numberToInvite = item.number;
const phoneInvitePromises = typeof conference === 'undefined'
? []
: phoneNumbers.map(item => {
const numberToInvite = item.number;
return conference.dial(numberToInvite)
return conference.dial(numberToInvite)
.then(() => {
invitesLeftToSend
= invitesLeftToSend.filter(
@@ -123,13 +130,13 @@ export function invite(
})
.catch((error: Error) =>
logger.error('Error inviting phone number:', error));
});
});
allInvitePromises = allInvitePromises.concat(phoneInvitePromises);
const usersAndRooms
= invitesLeftToSend.filter(
({ type }) => [ INVITE_TYPES.USER, INVITE_TYPES.ROOM ].includes(type));
({ type }) => [ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.ROOM ].includes(type));
if (usersAndRooms.length) {
// Send a request to invite all the rooms and users. On success,
@@ -139,12 +146,12 @@ export function invite(
(callFlowsEnabled
? inviteServiceCallFlowsUrl : inviteServiceUrl) ?? '',
inviteUrl,
jwt,
usersAndRooms)
usersAndRooms,
state)
.then(() => {
invitesLeftToSend
= invitesLeftToSend.filter(
({ type }) => ![ INVITE_TYPES.USER, INVITE_TYPES.ROOM ].includes(type));
({ type }) => ![ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.ROOM ].includes(type));
})
.catch(error => {
dispatch(setCalleeInfoVisible(false));

View File

@@ -61,6 +61,11 @@ export interface IProps {
*/
_peopleSearchQueryTypes: Array<string>;
/**
* The localStorage key holding the alternative token for people directory.
*/
_peopleSearchTokenLocation: string;
/**
* The URL pointing to the service allowing for people search.
*/
@@ -254,6 +259,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
_jwt: jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl,
_peopleSearchTokenLocation: peopleSearchTokenLocation,
_region: region,
_sipInviteEnabled: sipInviteEnabled
} = this.props;
@@ -266,6 +272,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
jwt,
peopleSearchQueryTypes,
peopleSearchUrl,
peopleSearchTokenLocation,
region,
sipInviteEnabled
};
@@ -295,7 +302,8 @@ export function _mapStateToProps(state: IReduxState) {
dialOutAuthUrl,
dialOutRegionUrl,
peopleSearchQueryTypes,
peopleSearchUrl
peopleSearchUrl,
peopleSearchTokenLocation
} = state['features/base/config'];
return {
@@ -308,6 +316,7 @@ export function _mapStateToProps(state: IReduxState) {
_jwt: state['features/base/jwt'].jwt ?? '',
_peopleSearchQueryTypes: peopleSearchQueryTypes ?? [],
_peopleSearchUrl: peopleSearchUrl ?? '',
_peopleSearchTokenLocation: peopleSearchTokenLocation ?? '',
_region: getMeetingRegion(state),
_sipInviteEnabled: isSipInviteEnabled(state)
};

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { matchesProperty, sortBy } from 'lodash-es';
import React, { ReactElement } from 'react';
import { WithTranslation } from 'react-i18next';
import {
@@ -19,6 +19,7 @@ import Icon from '../../../../base/icons/components/Icon';
import {
IconCheck,
IconCloseCircle,
IconEnvelope,
IconPhoneRinging,
IconSearch,
IconShare
@@ -260,6 +261,12 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
key: item.id || item.user_id,
title: item.name
};
case INVITE_TYPES.EMAIL:
return {
avatar: item.avatar || IconEnvelope,
key: item.id || item.user_id,
title: item.name
};
default:
return null;
}
@@ -273,7 +280,11 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
* @returns {string}
*/
_keyExtractor(item: any) {
return item.type === INVITE_TYPES.USER ? item.id || item.user_id : item.number;
if (item.type === INVITE_TYPES.USER || item.type === INVITE_TYPES.EMAIL) {
return item.id || item.user_id;
}
return item.number;
}
/**
@@ -320,7 +331,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
const finderKey = item.type === INVITE_TYPES.PHONE ? 'number' : 'user_id';
if (inviteItems.find(
_.matchesProperty(finderKey, item[finderKey as keyof typeof item]))) {
matchesProperty(finderKey, item[finderKey as keyof typeof item]))) {
// Item is already selected, need to unselect it.
this.setState({
inviteItems: inviteItems.filter(
@@ -332,7 +343,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
const items = inviteItems.concat(item);
this.setState({
inviteItems: _.sortBy(items, [ 'name', 'number' ])
inviteItems: sortBy(items, [ 'name', 'number' ])
});
}
};
@@ -383,7 +394,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
_performSearch(query: string) {
this._query(query).then(results => {
this.setState({
selectableItems: _.sortBy(results, [ 'name', 'number' ])
selectableItems: sortBy(results, [ 'name', 'number' ])
});
})
.finally(() => {
@@ -448,12 +459,13 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
switch (item.type) {
case INVITE_TYPES.PHONE:
selected = inviteItems.find(_.matchesProperty('number', item.number));
selected = inviteItems.find(matchesProperty('number', item.number));
break;
case INVITE_TYPES.USER:
case INVITE_TYPES.EMAIL:
selected = item.id
? inviteItems.find(_.matchesProperty('id', item.id))
: inviteItems.find(_.matchesProperty('user_id', item.user_id));
? inviteItems.find(matchesProperty('id', item.id))
: inviteItems.find(matchesProperty('user_id', item.user_id));
break;
default:
return null;

View File

@@ -7,7 +7,7 @@ import { IReduxState, IStore } from '../../../../app/types';
import Avatar from '../../../../base/avatar/components/Avatar';
import { translate } from '../../../../base/i18n/functions';
import Icon from '../../../../base/icons/components/Icon';
import { IconPhoneRinging } from '../../../../base/icons/svg';
import { IconEnvelope, IconPhoneRinging, IconUser } from '../../../../base/icons/svg';
import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
import Button from '../../../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../../../base/ui/constants.any';
@@ -302,9 +302,12 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
* @returns {ReactElement}
*/
_getAvatar(user: any, className = 'avatar-small') {
const defaultIcon = user.type === INVITE_TYPES.EMAIL ? IconEnvelope : IconUser;
return (
<Avatar
className = { className }
defaultIcon = { defaultIcon }
size = { 32 }
status = { user.status }
url = { user.avatar } />
@@ -325,7 +328,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
_parseQueryResults(response: IInvitee[] = []) {
const { t, _dialOutEnabled } = this.props;
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
const users = response.filter(item => userTypes.includes(item.type));
const userDisplayItems: any = [];

View File

@@ -48,6 +48,7 @@ export const SIP_ADDRESS_REGEX = /^[+a-zA-Z0-9]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)
* Different invite types mapping.
*/
export const INVITE_TYPES = {
EMAIL: 'email',
PHONE: 'phone',
ROOM: 'room',
SIP: 'sip',

View File

@@ -1,3 +1,6 @@
// @ts-expect-error
import { jitsiLocalStorage } from '@jitsi/js-utils';
import { IReduxState } from '../app/types';
import { IStateful } from '../base/app/types';
import { getRoomName } from '../base/conference/functions';
@@ -143,6 +146,11 @@ export type GetInviteResultsOptions = {
*/
peopleSearchQueryTypes: Array<string>;
/**
* Key in localStorage holding the alternative token for people directory.
*/
peopleSearchTokenLocation?: string;
/**
* The url to query for people.
*/
@@ -181,6 +189,7 @@ export function getInviteResultsForQuery(
dialOutEnabled,
peopleSearchQueryTypes,
peopleSearchUrl,
peopleSearchTokenLocation,
region,
sipInviteEnabled,
jwt
@@ -193,7 +202,8 @@ export function getInviteResultsForQuery(
peopleSearchUrl,
jwt,
text,
peopleSearchQueryTypes);
peopleSearchQueryTypes,
peopleSearchTokenLocation);
} else {
peopleSearchPromise = Promise.resolve([]);
}
@@ -411,29 +421,40 @@ export function getInviteTypeCounts(inviteItems: IInvitee[] = []) {
* @param {string} inviteServiceUrl - The invite service that generates the
* invitation.
* @param {string} inviteUrl - The url to the conference.
* @param {string} jwt - The jwt token to pass to the search service.
* @param {Immutable.List} inviteItems - The list of the "user" or "room" type
* items to invite.
* @param {IReduxState} state - Global state.
* @returns {Promise} - The promise created by the request.
*/
export function invitePeopleAndChatRooms(
inviteServiceUrl: string,
inviteUrl: string,
jwt: string,
inviteItems: Array<Object>
inviteItems: Array<Object>,
state: IReduxState
): Promise<any> {
if (!inviteItems || inviteItems.length === 0) {
return Promise.resolve();
}
// Parse all the query strings of the search directory endpoint
const { jwt = '' } = state['features/base/jwt'];
const { peopleSearchTokenLocation } = state['features/base/config'];
let token = jwt;
// If token is empty, check for alternate token
if (!token && peopleSearchTokenLocation) {
token = jitsiLocalStorage.getItem(peopleSearchTokenLocation) ?? '';
}
const headers = {
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
...token ? { 'Authorization': `Bearer ${token}` } : {},
'Content-Type': 'application/json'
};
return fetch(
`${inviteServiceUrl}`,
inviteServiceUrl,
{
body: JSON.stringify({
'invited': inviteItems,
@@ -452,9 +473,14 @@ export function invitePeopleAndChatRooms(
* @returns {boolean} Indication of whether adding people is currently enabled.
*/
export function isAddPeopleEnabled(state: IReduxState): boolean {
const { peopleSearchUrl } = state['features/base/config'];
const {
peopleSearchUrl,
peopleSearchTokenLocation
} = state['features/base/config'];
return Boolean(state['features/base/jwt'].jwt && Boolean(peopleSearchUrl) && !isVpaasMeeting(state));
const hasToken = Boolean(state['features/base/jwt'].jwt || Boolean(peopleSearchTokenLocation));
return Boolean(hasToken && Boolean(peopleSearchUrl) && !isVpaasMeeting(state));
}
/**
@@ -534,21 +560,30 @@ function isPhoneNumberRegex(): RegExp {
* @param {string} jwt - The jwt token to pass to the search service.
* @param {string} text - Text to search.
* @param {Array<string>} queryTypes - Array with the query types that will be
* executed - "conferenceRooms" | "user" | "room".
* executed - "conferenceRooms" | "user" | "room" | "email".
* @param {string} peopleSearchTokenLocation - The localStorage key holding the token value for alternate auth.
* @returns {Promise} - The promise created by the request.
*/
export function searchDirectory( // eslint-disable-line max-params
serviceUrl: string,
jwt: string,
text: string,
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room', 'email' ],
peopleSearchTokenLocation?: string
): Promise<Array<{ type: string; }>> {
const query = encodeURIComponent(text);
const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
let token = jwt;
// If token is empty, check for alternate token
if (!token && peopleSearchTokenLocation) {
token = jitsiLocalStorage.getItem(peopleSearchTokenLocation) ?? '';
}
const headers = {
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {}
...token ? { 'Authorization': `Bearer ${token}` } : {}
};
return fetch(`${serviceUrl}?query=${query}&queryTypes=${

View File

@@ -1,4 +1,4 @@
import _ from 'lodash';
import { sortBy } from 'lodash-es';
import React, { Component } from 'react';
import { NativeModules, Text, TextStyle, TouchableHighlight, View, ViewStyle } from 'react-native';
import { connect } from 'react-redux';
@@ -201,7 +201,7 @@ class AudioRoutePickerDialog extends Component<IProps, IState> {
// Make sure devices is alphabetically sorted.
return {
devices: _.sortBy(audioDevices, 'text')
devices: sortBy(audioDevices, 'text')
};
}

View File

@@ -18,14 +18,3 @@ export const READY_TO_CLOSE = 'READY_TO_CLOSE';
*/
export const SCREEN_SHARE_PARTICIPANTS_UPDATED
= 'SCREEN_SHARE_PARTICIPANTS_UPDATED';
/**
* The type of (redux) action which signals that a custom button from the overflow menu was pressed.
*
* @returns {{
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
* id: string,
* text: string
* }}
*/
export const CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED = 'CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED';

View File

@@ -1,8 +1,4 @@
import {
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
READY_TO_CLOSE,
SCREEN_SHARE_PARTICIPANTS_UPDATED
} from './actionTypes';
import { READY_TO_CLOSE, SCREEN_SHARE_PARTICIPANTS_UPDATED } from './actionTypes';
/**
@@ -35,22 +31,3 @@ export function setParticipantsWithScreenShare(participantIds: Array<string>) {
participantIds
};
}
/**
* Creates a (redux) action which that a custom overflow menu button was pressed.
*
* @param {string} id - The id for the custom button.
* @param {string} text - The label for the custom button.
* @returns {{
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
* id: string,
* text: string
* }}
*/
export function customOverflowMenuButtonPressed(id: string, text: string) {
return {
type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
id,
text
};
}

View File

@@ -1,4 +1,4 @@
import debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
import { NativeModules } from 'react-native';
import { IParticipant } from '../../base/participants/types';

View File

@@ -1,6 +1,6 @@
/* eslint-disable lines-around-comment */
import debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
import { NativeEventEmitter, NativeModules } from 'react-native';
import { AnyAction } from 'redux';
@@ -52,15 +52,13 @@ import { ITrack } from '../../base/tracks/types';
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
import { closeChat, openChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.native';
import { setRequestingSubtitles } from '../../subtitles/actions.any';
import { CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED } from '../../toolbox/actionTypes';
import { muteLocal } from '../../video-menu/actions.native';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
// @ts-ignore
import { isExternalAPIAvailable } from '../react-native-sdk/functions';
import {
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
READY_TO_CLOSE
} from './actionTypes';
import { READY_TO_CLOSE } from './actionTypes';
import { setParticipantsWithScreenShare } from './actions';
import { participantToParticipantInfo, sendEvent } from './functions';
import logger from './logger';
@@ -82,12 +80,6 @@ const CHAT_TOGGLED = 'CHAT_TOGGLED';
*/
const CONFERENCE_TERMINATED = 'CONFERENCE_TERMINATED';
/**
* Event which will be emitted on the native side to indicate that the custom overflow menu button was pressed.
*/
const CUSTOM_MENU_BUTTON_PRESSED = 'CUSTOM_MENU_BUTTON_PRESSED';
/**
* Event which will be emitted on the native side to indicate a message was received
* through the channel.
@@ -199,7 +191,7 @@ externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
sendEvent(
store,
CUSTOM_MENU_BUTTON_PRESSED,
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
{
id,
text

Some files were not shown because too many files have changed in this diff Show More