Compare commits

...

70 Commits
8317 ... 8366

Author SHA1 Message Date
Hristo Terezov
a3622a3698 ref(SS): remove legacy SS for electron. 2025-01-21 21:22:52 -06:00
Saúl Ibarra Corretgé
4cdc193ac3 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1900.0.0+250ff7ed...v1901.0.0+22daa357
2025-01-21 20:22:38 +01:00
Saúl Ibarra Corretgé
5c9b8a5a5f fix(device-selection) hide framerate selection on mobile browsers
Screen-sharing is not supported there.
2025-01-21 14:04:54 +01:00
Wilson Furtado
44bd70179c Added tooltip when roomname is elided in the pre meeting screen (#15508)
* added tooltip when roomname overflows
2025-01-20 11:46:57 +02:00
Дамян Минков
0973081fea fix(share-video): Hide element when not shown on large. (#15507)
* fix(share-video): Hide element when not shown on large.

Fixes two issues:
- disabling mouse for all large video types, including local shared desktop that prevents clicking the link to show content
- as shared-video z-index is on top of everything, it local shared desktop to be seen when the thumbnail is clicked

* squash: Drop the video shared component from the dom when not playing.
2025-01-17 16:28:40 -06:00
Jaya Allamsetty
006c491de4 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1897.0.0+283af7ac...v1900.0.0+250ff7ed
2025-01-17 10:32:03 -05:00
Felipe Amaral
24cfa7b4e1 fix(notifications): Removed info type from docs (#15504)
There is no info type in showNotificantion command.
2025-01-17 09:17:56 -06:00
Calinteodor
b890aa33c3 feat(react-native-sdk/android): force permissions approval in order to launch RNOngoingNotification (#15400)
Re-added visibility control for ongoing conference and media projection notifications on our React Native SDK.
2025-01-17 17:03:30 +02:00
Saúl Ibarra Corretgé
9846228210 feat(android) bump minimum required version to 26 aka Oreo
There is a 1.2% cummulative usage change according to [0] and it allows
us to simplify some code going forward.

[0]: https://apilevels.com/
2025-01-17 13:06:02 +02:00
Hristo Terezov
445eef97b3 fix(interface-config-whitelist): cleanup
Removes the following unused props.
2025-01-16 18:19:44 -06:00
Calin-Teodor
298279a956 feat(toolbox): reactions menu native ui adjustments 2025-01-16 21:34:22 +02:00
Hristo Terezov
5ebf5adfa2 fix(configWhitelist): alphasort 2025-01-16 12:44:51 -06:00
Hristo Terezov
35554533d1 fix(config): Document desktopSharingSources and improve types 2025-01-16 12:44:51 -06:00
Hristo Terezov
fb397db69f fix(logging-config): Improve types 2025-01-16 12:44:51 -06:00
Hristo Terezov
8fc295b385 fix(config): Document googleApiApplicationClientID 2025-01-16 12:44:51 -06:00
Hristo Terezov
ee8b62cf73 fix(config): Document LJM props.
disableAEC
disableAGC
disableAP
disableNS
displayJids
forceTurnRelay
ignoreStartMuted
enableTalkWhileMuted
2025-01-16 12:44:51 -06:00
Hristo Terezov
217b803652 ref(config): Remove legacy multistream backward compat flags 2025-01-16 12:44:51 -06:00
Hristo Terezov
2442a332e7 ref(config): Move config types from reducer to IConfig type
The properties are - visitors and disableRemoteControl.
2025-01-16 12:44:51 -06:00
Hristo Terezov
2e43706a4a fix(config): Remove unused options.
firefox_fake_device
enableAutomaticUrlCopy
fileRecordingsEnabled
disableHPF
2025-01-16 12:44:51 -06:00
Hristo Terezov
e7db943b73 fix(config): Document testing.failICE 2025-01-16 12:44:51 -06:00
Hristo Terezov
c1cbd37d87 fix(config): Move debugAudioLelvels in config.testing 2025-01-16 12:44:51 -06:00
Hristo Terezov
ef56f1d23d fix(config): Remove config.debug.
Instead we use config.testing?.testMode.
2025-01-16 12:44:51 -06:00
Hristo Terezov
2ab002650c fix(conference): Remove unused argument from _createDesktopTrack 2025-01-16 12:44:51 -06:00
Calin-Teodor
867646da1a feat(ios): fixed path from where we take hermes.xcframework 2025-01-16 18:17:00 +02:00
Saúl Ibarra Corretgé
ff78bfb62f fix(ios) fix node path in scripts 2025-01-16 17:11:36 +02:00
Saúl Ibarra Corretgé
1827610fc1 fix(subtitles) fix skipping transcription messages
If we are not requesting any transcription, `language` will be `null` so
take that into consideration when checking if we should stop processing
a transcription message after firing the API event.

Fixes: https://github.com/jitsi/docker-jitsi-meet/issues/1997
2025-01-16 13:21:51 +01:00
Hristo Terezov
0ae8051fb4 fix(config): Allow only enableMediaOnPromote from visitors config to be overriden. 2025-01-15 15:50:48 -06:00
damencho
1b7668bbdc feat(dialog): Adds a print when opening and hiding dialogs. 2025-01-15 10:49:20 -06:00
damencho
4c37e0d761 feat(speakerstats): Shows a tooltip for participants count. 2025-01-15 10:49:20 -06:00
damencho
a6ff652a03 feat(visitors): Start shortening the number above 1000. 2025-01-15 10:49:20 -06:00
damencho
17f34878db feat(visitors): Renames visitors/obeserver to viewers.
Changes only strings visible in the UI.
2025-01-15 10:49:20 -06:00
Saúl Ibarra Corretgé
37856e0fdb fix(external_api) drop legacy constructor arguments
The new format (using an options object) was introduced 7 years ago.
It's about time.
2025-01-14 15:38:11 +01:00
Edgars Voroboks
7c1de52f6a fix(lang): Update Latvian language translation 2025-01-14 06:20:12 -06:00
Jaya Allamsetty
9e3438696b chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1895.0.0+7de7d9aa...v1897.0.0+283af7ac
2025-01-13 12:46:47 -05:00
Andrei Gavrilescu
9c23cc0b70 feat(rnnoise): update rnnoise-wasm version (#15478)
* update rnnoise-wasm version

* update expected worklet size
2025-01-13 14:11:32 +02:00
Hristo Terezov
af8157bc06 fix(configWhitelist): Add p2p.mobileScreenshareCodec 2025-01-10 17:38:12 -06:00
Дамян Минков
c21f84c15a feat(tests): Adds follow-me and invite dialog test. (#15476)
* feat(tests): Adds follow-me test.

* feat(tests): Adds invite dialog test.

* squash: fix lint.
2025-01-10 16:47:25 -06:00
Saúl Ibarra Corretgé
1edf88e744 fix(lint) tame linter 2025-01-10 20:09:11 +01:00
Saúl Ibarra Corretgé
bad75b488e chore(deps) update eslint
Required for compatibility with new TypeScript plugins.
2025-01-10 20:09:11 +01:00
Saúl Ibarra Corretgé
5bb3ba71d0 chore(helpers) drop custom createDeferred() for Promise.withResolvers() 2025-01-10 20:09:11 +01:00
Saúl Ibarra Corretgé
4e0001c9af fix(polls) limit maximum amount of answers 2025-01-10 13:51:30 +01:00
Дамян Минков
ada6150971 feat(tests): Adds dial-in test. (#15470)
* feat(tests): Adds dial-in test.

* feat(tests): Adds fake dial-in test.

* squash: switch to performance.now().
2025-01-10 06:28:53 -06:00
damencho
62d1ee606a feat(tests): Adds desktop sharing test. 2025-01-09 18:29:47 -06:00
simplify123
1e1de6acf0 lang: Update main-zhCN.json (#15426)
main-zhCN.json 
line 413 missing:
"sessTerminatedReason": "会议已经结束",
2025-01-09 15:04:23 -06:00
damencho
e1e0fba4ff fix(readme): Fixes badge icon.
Fixes #15464
2025-01-09 15:04:02 -06:00
Jaya Allamsetty
97be3fa399 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1893.0.0+496b64a7...v1895.0.0+7de7d9aa
2025-01-09 15:22:18 -05:00
damencho
fbdee85c4b fix(readme): Fixes badge icon and f-droid link.
Fixes #15464
Fixes #15465
2025-01-09 12:38:11 -06:00
Bhavik Arora
b71b4c8c45 fix(video-menu): Hide "Mute all" option when all participants are moderators (#15461)
* Fixed exception for visibility state of mute all button

* Updated changes as per review: shifted conditional to the toggle

* chore: fix eslint warnings and errors

---------

Co-authored-by: Bhavik Arora <arorabhavik1001>
2025-01-09 10:21:28 -06:00
Florian
a8d123ede4 fix(lang) fix typo in unsecure room name warning (#15468) 2025-01-09 17:01:23 +01:00
Hristo Terezov
fb8e451e2e feat(customParticipantButton): metrics 2025-01-08 11:11:21 -06:00
damencho
5dd7944bdb fix(connection): Adds default tenant value on error.
Avoids undefined error.
2025-01-08 08:36:35 -06:00
Jaya Allamsetty
48732c6784 chore(deps) lib-jitsi-meet@latest
https://github.com/jitsi/lib-jitsi-meet/compare/v1890.0.0+144b0cab...v1893.0.0+496b64a7
2025-01-07 21:45:03 -06:00
Hristo Terezov
97146ed8a7 fix(configWhitelist): Remove some props. 2025-01-07 15:32:08 -06:00
Дамян Минков
78b17c8d17 feat(tests): Adds self view, display name and end conference tests. (#15432)
* feat(tests): Use shorter display names for screenshots.

* feat(tests): Adds self view tests.

* feat(tests): Adds display name test.

* feat(tests): Adds end conference test.
2025-01-07 14:55:43 -06:00
Saúl Ibarra Corretgé
9732d1be86 fix(polls) improve resilience to bogus data 2025-01-07 16:06:10 +01:00
Hristo Terezov
a836187620 feat(URL-overrides): Add metrics. 2025-01-06 09:35:36 -06:00
Wilson Furtado
a0cad4c595 fix(notification) add tabIndex to close Icon in the notification Item 2025-01-06 10:13:06 +01:00
Wilson Furtado
19b9eac84a fix(input) fix content overlapping when clearable icon is present 2025-01-06 10:12:14 +01:00
Wilson Furtado
068e33fc0f fix(toolbar): Removed unnecessary re render of the reactions component (#15433)
Added usememo to remove unnecessary re render. Fixes #15434.
2024-12-31 10:04:47 -06:00
Wilson Furtado
00c6bee2fd feat(welcome-page): added hover fill to trash icon (#15431)
Fixes #15430.
2024-12-30 16:11:43 -06:00
Wilson Furtado
748ead7e13 feat: Added hover styles and added tabIndex to the meeting list container instead of the meeting item header (#15429)
* added hover styles and added tabIndex to the container instead of the meeting heading

* lint error fix
2024-12-30 14:05:56 -06:00
damencho
174c4418fb fix(prosody): Adds another condition to the filter. 2024-12-26 12:12:53 -06:00
Damien Fetis
64494cab81 lang: Update missing french labels (#15422) 2024-12-23 18:28:35 -06:00
damencho
50d0092e30 fix(shared-video): Remove disable button action from web. 2024-12-23 07:46:25 -06:00
damencho
fdbd7239ff fix(shared-video): Gets from info from the incoming presence.
Ignore using from field send in attributes of the command.
2024-12-23 07:46:25 -06:00
Дамян Минков
a30958ab23 feat(tests): Adds chat panel and codec selection tests. (#15416)
* fix(tests): Another attempt to fix Firefox excludes.

Drawback is that it will be a little bit slow.

* feat(tests): Adds chatPanel tests.

* feat(tests): Adds codec selection tests.
2024-12-20 17:04:07 -06:00
damencho
aca55172e4 fix(config): Fixes wrong file location. 2024-12-20 11:37:48 -06:00
Дамян Минков
c6cce9253c feat(tests): Adds breakout tests. (#15414)
* feat(tests): Introduces BasePageObject.

* fix(tests): Use wdio aria selector where possible.

* fix(tests): Correct test exclusion for Firefox.

* fix(tests): Rearrange code.

* feat(tests): Adds breakout tests.
2024-12-20 06:17:49 -06:00
Saúl Ibarra Corretgé
c23684e11c fix(devices) filter out Zoom audio device
I haven't seen any particular problem with it, but it's of no use
anyway...
2024-12-20 07:58:48 +01:00
luzpaz
30595584a3 chore(typos) fix various typos
Found via `codespell -q 3 -S "*.svg,./lang" -L anser,bu,dialin,goup,miliseconds,nd,vew`
2024-12-20 07:58:31 +01:00
206 changed files with 6349 additions and 2223 deletions

3
.gitignore vendored
View File

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

View File

@@ -34,7 +34,7 @@ mobile apps:
| Android | Android (F-Droid) | iOS |
|:-:|:-:|:-:|
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/en/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
| [<img src="resources/img/google-play-badge.png" height="50">](https://play.google.com/store/apps/details?id=org.jitsi.meet) | [<img src="resources/img/f-droid-badge.png" height="50">](https://f-droid.org/packages/org.jitsi.meet/) | [<img src="resources/img/appstore-badge.png" height="50">](https://itunes.apple.com/us/app/jitsi-meet/id1165103905) |
If you are feeling adventurous and want to get an early scoop of the features as they are being
developed you can also sign up for our open beta testing here:

View File

@@ -20,7 +20,7 @@ ext {
kotlinVersion = "1.9.24"
buildToolsVersion = "34.0.0"
compileSdkVersion = 34
minSdkVersion = 24
minSdkVersion = 26
targetSdkVersion = 34
supportLibVersion = "28.0.0"
ndkVersion = "26.1.10909125"

View File

@@ -18,7 +18,6 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import android.telecom.CallAudioState;
import androidx.annotation.RequiresApi;
@@ -34,7 +33,6 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
* {@link AudioModeModule.AudioDeviceHandlerInterface} module implementing device handling for
* Android versions >= O when ConnectionService is enabled.
*/
@RequiresApi(Build.VERSION_CODES.O)
class AudioDeviceHandlerConnectionService implements
AudioModeModule.AudioDeviceHandlerInterface,
RNConnectionService.CallAudioStateListener {

View File

@@ -20,7 +20,6 @@ import android.media.AudioAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import java.util.HashSet;
import java.util.Set;
@@ -227,22 +226,17 @@ class AudioDeviceHandlerGeneric implements
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
int gotFocus;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
} else {
gotFocus = audioManager.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN);
}
int gotFocus = audioManager.requestAudioFocus(new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAudioAttributes(
new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(this)
.build()
);
if (gotFocus == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
JitsiMeetLogger.w(TAG + " Audio focus request failed");

View File

@@ -20,7 +20,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
@@ -58,7 +57,6 @@ import java.util.concurrent.Executors;
* Before a call has started and after it has ended the
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
@SuppressLint("AnnotateVersionCheck")
@ReactModule(name = AudioModeModule.NAME)
class AudioModeModule extends ReactContextBaseJavaModule {
public static final String NAME = "AudioMode";
@@ -84,11 +82,10 @@ class AudioModeModule extends ReactContextBaseJavaModule {
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
private static final boolean supportsConnectionService = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
private static boolean useConnectionService_ = supportsConnectionService;
private static boolean useConnectionService_ = true;
static boolean useConnectionService() {
return supportsConnectionService && useConnectionService_;
return useConnectionService_;
}
/**

View File

@@ -37,7 +37,6 @@ import java.util.Objects;
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class ConnectionService extends android.telecom.ConnectionService {
/**

View File

@@ -42,7 +42,7 @@ public class JitsiMeetActivityDelegate {
/**
* Tells whether or not the permissions request is currently in progress.
*
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
* @return {@code true} if the permissions are being requested or {@code false} otherwise.
*/
static boolean arePermissionsBeingRequested() {
return permissionListener != null;

View File

@@ -83,11 +83,7 @@ public class JitsiMeetOngoingConferenceService extends Service implements Ongoin
ComponentName componentName;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
componentName = context.startForegroundService(intent);
} else {
componentName = context.startService(intent);
}
componentName = context.startForegroundService(intent);
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions

View File

@@ -29,8 +29,6 @@ import android.content.Intent;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import android.os.Build;
/**
* Helper class for creating the ongoing notification which is used with
@@ -45,10 +43,6 @@ class OngoingNotification {
static final String ONGOING_CONFERENCE_CHANNEL_ID = "JitsiOngoingConferenceChannel";
static void createNotificationChannel(Activity context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;

View File

@@ -20,7 +20,6 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.PictureInPictureParams;
import android.os.Build;
import android.util.Rational;
import com.facebook.react.bridge.Promise;
@@ -53,7 +52,7 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
// Android Go devices don't support PiP. There doesn't seem to be a better way to detect it than
// to use ActivityManager.isLowRamDevice().
// https://stackoverflow.com/questions/58340558/how-to-detect-android-go
isSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !am.isLowRamDevice();
isSupported = !am.isLowRamDevice();
}
/**
@@ -82,7 +81,6 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
* including when the activity is not visible (paused or stopped), if the
* screen is locked or if the user has an activity pinned.
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isEnabled) {
return;

View File

@@ -3,7 +3,6 @@ package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
@@ -32,7 +31,6 @@ import org.jitsi.meet.sdk.log.JitsiMeetLogger;
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME)
class RNConnectionService extends ReactContextBaseJavaModule {
@@ -53,7 +51,6 @@ class RNConnectionService extends ReactContextBaseJavaModule {
* @param audioRoute the new audio route to be set. See
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
*/
@RequiresApi(api = Build.VERSION_CODES.O)
static void setAudioRoute(int audioRoute) {
for (ConnectionService.ConnectionImpl c
: ConnectionService.getConnections()) {

View File

@@ -1012,7 +1012,7 @@ export default {
},
/**
* Will be filled with values only when config.debug is enabled.
* Will be filled with values only when config.testing.testMode is true.
* Its used by torture to check audio levels.
*/
audioLevelsMap: {},
@@ -1399,28 +1399,19 @@ export default {
/**
* Creates desktop (screensharing) {@link JitsiLocalTrack}
*
* @param {Object} [options] - Screen sharing options that will be passed to
* createLocalTracks.
* @param {Object} [options.desktopSharing]
* @param {Object} [options.desktopStream] - An existing desktop stream to
* use instead of creating a new desktop stream.
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
* {@link JitsiLocalTrack} for the screensharing or rejected with
* {@link JitsiTrackError}.
*
* @private
*/
_createDesktopTrack(options = {}) {
_createDesktopTrack() {
const didHaveVideo = !this.isLocalVideoMuted();
const getDesktopStreamPromise = options.desktopStream
? Promise.resolve([ options.desktopStream ])
: createLocalTracksF({
desktopSharingSourceDevice: options.desktopSharingSources
? null : config._desktopSharingSourceDevice,
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ]
});
const getDesktopStreamPromise = createLocalTracksF({
desktopSharingSourceDevice: config._desktopSharingSourceDevice,
devices: [ 'desktop' ]
});
return getDesktopStreamPromise.then(desktopStreams => {
// Stores the "untoggle" handler which remembers whether was
@@ -1597,9 +1588,9 @@ export default {
newLvl = 0;
}
if (config.debug) {
if (config.testing?.testMode) {
this.audioLevelsMap[id] = newLvl;
if (config.debugAudioLevels) {
if (config.testing?.debugAudioLevels) {
logger.log(`AudioLevel:${id}/${newLvl}`);
}
}

View File

@@ -103,6 +103,12 @@ var config = {
// Dump transcripts to a <transcript> element for debugging.
// dumpTranscript: false,
// Log the audio levels.
// debugAudioLevels: true,
// Will replace ice candidates IPs with invalid ones in order to fail ice.
// failICE: true,
},
// Disables moderator indicators.
@@ -362,9 +368,6 @@ var config = {
// Recording
// DEPRECATED. Use recordingService.enabled instead.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>', // Specify your app key here.
@@ -459,7 +462,7 @@ var config = {
// // Translation languages.
// // Available languages can be found in
// // ./src/react/features/transcribing/translation-languages.json.
// // ./lang/translation-languages.json.
// translationLanguages: ['en', 'es', 'fr', 'ro'],
// // Important languages to show on the top of the language list.
@@ -785,6 +788,9 @@ var config = {
// and microsoftApiApplicationClientID
// enableCalendarIntegration: false,
// The client id for the google APIs used for the calendar integration, youtube livestreaming, etc.
// googleApiApplicationClientID: '<client_id>',
// Configs for prejoin page.
// prejoinConfig: {
// // When 'true', it shows an intermediate page before joining, where the user can configure their devices.
@@ -819,10 +825,6 @@ var config = {
// set or the lobby is not enabled.
// enableInsecureRoomNameWarning: false,
// Whether to automatically copy invitation URL after creating a room.
// Document should be focused for this option to work
// enableAutomaticUrlCopy: false,
// Array with avatar URL prefixes that need to use CORS.
// corsAvatarURLs: [ 'https://www.gravatar.com/avatar/' ],
@@ -907,7 +909,7 @@ var config = {
// Overrides the buttons displayed in the main toolbar. Depending on the screen size the number of displayed
// buttons varies from 2 buttons to 8 buttons. Every array in the mainToolbarButtons array will replace the
// corresponding default buttons configuration matched by the number of buttons specified in the array. Arrays with
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a cerain
// more than 8 buttons or less then 2 buttons will be ignored. When there there isn't an override for a certain
// configuration (for example when 3 buttons are displayed) the default jitsi-meet configuration will be used.
// The order of the buttons in the array is preserved.
// mainToolbarButtons: [
@@ -1620,19 +1622,47 @@ var config = {
// For external entities (e. g. email), the localStorage key holding the token value for directory authentication
// peopleSearchTokenLocation: "mytoken",
// Options related to visitors.
// visitors: {
// // Starts audio/video when the participant is promoted from visitor.
// enableMediaOnPromote: {
// audio: true,
// video: true
// },
// },
// The default type of desktop sharing sources that will be used in the electron app.
// desktopSharingSources: ['screen', 'window'],
// Disables the echo cancelation for local audio tracks.
// disableAEC: true,
// Disables the auto gain control for local audio tracks.
// disableAGC: true,
// Disables the audio processing (echo cancelation, auto gain control and noise suppression) for local audio tracks.
// disableAP: true,
// Disables the anoise suppression for local audio tracks.
// disableNS: true,
// Replaces the display name with the JID of the participants.
// displayJids: true,
// Enables disables talk while muted detection.
// enableTalkWhileMuted: true,
// Sets the peer connection ICE transport policy to "relay".
// forceTurnRelay: true,
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
debug
debugAudioLevels
deploymentInfo
dialOutAuthUrl
dialOutCodesUrl
dialOutRegionUrl
disableRemoteControl
displayJids
firefox_fake_device
googleApiApplicationClientID
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID
@@ -1651,14 +1681,7 @@ var config = {
_peerConnStatusRtcMuteTimeout
avgRtpStatsN
desktopSharingSources
disableAEC
disableAGC
disableAP
disableHPF
disableLocalStats
disableNS
enableTalkWhileMuted
forceTurnRelay
hiddenDomain
hiddenFromRecorderFeatureEnabled
ignoreStartMuted
@@ -1735,7 +1758,7 @@ var config = {
// 'notify.participantsWantToJoin', // shown when lobby is enabled and participants request to join meeting
// 'notify.passwordRemovedRemotely', // shown when a password has been removed remotely
// 'notify.passwordSetRemotely', // shown when a password has been set remotely
// 'notify.raisedHand', // shown when a partcipant used raise hand,
// 'notify.raisedHand', // shown when a participant used raise hand,
// 'notify.screenShareNoAudio', // shown when the audio could not be shared for the selected screen
// 'notify.screenSharingAudioOnlyTitle', // shown when the best performance has been affected by screen sharing
// 'notify.selfViewTitle', // show "You can always un-hide the self-view from settings"
@@ -1766,7 +1789,7 @@ var config = {
// disableFilmstripAutohiding: false,
// filmstrip: {
// // Disable the vertical/horizonal filmstrip.
// // Disable the vertical/horizontal filmstrip.
// disabled: false,
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
// // (width, tiles aspect ratios) through the interfaceConfig options.
@@ -1825,9 +1848,10 @@ var config = {
// //disableLogCollector: true,
// // Individual loggers are customizable.
// loggers: {
// // The following are too verbose in their logging with the default level.
// 'modules/RTC/TraceablePeerConnection.js': 'info',
// 'modules/xmpp/strophe.util.js': 'log',
// // The following are too verbose in their logging with the default level.
// 'modules/RTC/TraceablePeerConnection.js': 'info',
// 'modules/xmpp/strophe.util.js': 'log',
// },
// },
// Application logo url
@@ -1885,12 +1909,6 @@ var config = {
// hideLoginButton: true,
};
// Temporary backwards compatibility with old mobile clients.
config.flags = config.flags || {};
config.flags.sourceNameSignaling = true;
config.flags.sendMultipleVideoStreams = true;
config.flags.receiveMultipleVideoStreams = true;
// Set the default values for JaaS customers
if (enableJaaS) {
config.dialInNumbersUrl = 'https://conference-mapper.jitsi.net/v1/access/dids';

View File

@@ -19,11 +19,11 @@
flex-direction: column;
.description {
color: #2f3237;
font-size: 14px;
line-height: 18px;
margin-bottom: 16px;
max-width: 436px;
color: #2f3237;
font-size: 14px;
line-height: 18px;
margin-bottom: 16px;
max-width: 436px;
}
}
@@ -127,7 +127,8 @@
cursor: pointer;
}
&.with-click-handler:hover {
&.with-click-handler:hover,
&.with-click-handler:focus {
background-color: #c7ddff;
}
@@ -155,14 +156,22 @@
margin-right: 16px;
position: absolute;
&> svg {
&>svg {
fill: #0074e0;
}
}
.item:hover, .item:focus, .item:focus-within {
.item:hover,
.item:focus,
.item:focus-within {
.delete-meeting {
display: block;
}
.delete-meeting:hover {
&>svg {
fill: #4687ED;
}
}
}
}
}

View File

@@ -175,22 +175,6 @@ case "$1" in
fi
fi
# Fixes multi-stream flags to workaround problem with mobile joining a multi-stream call with multi-stream disabled
FIX_MSG="// Temporary backwards compatibility with old mobile clients."
if ! grep -q "^${FIX_MSG}" $JITSI_MEET_CONFIG; then
echo $FIX_MSG >> $JITSI_MEET_CONFIG
echo "config.flags = config.flags || {};" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sourceNameSignaling*" $JITSI_MEET_CONFIG; then
echo "config.flags.sourceNameSignaling = true;" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.sendMultipleVideoStreams*" $JITSI_MEET_CONFIG; then
echo "config.flags.sendMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
fi
if ! grep -q "^config.flags.receiveMultipleVideoStreams*" $JITSI_MEET_CONFIG; then
echo "config.flags.receiveMultipleVideoStreams = true;" >> $JITSI_MEET_CONFIG
fi
if [[ "$FORCE_OPENRESTY" = "true" ]]; then
NGX_COMMON_CONF_PATH="/usr/local/openresty/nginx/conf/$JVB_HOSTNAME.conf"
NGX_SVC_NAME=openresty

1
globals.d.ts vendored
View File

@@ -18,7 +18,6 @@ declare global {
JITSI_MEET_LITE_SDK?: boolean;
interfaceConfig?: any;
JitsiMeetJS?: any;
JitsiMeetElectron?: any;
PressureObserver?: any;
PressureRecord?: any;
ReactNativeWebView?: any;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -41,14 +41,6 @@ var interfaceConfig = {
*/
DISABLE_PRESENCE_STATUS: false,
/**
* Whether the ringing sound in the call/ring overlay is disabled. If
* {@code undefined}, defaults to {@code false}.
*
* @type {boolean}
*/
DISABLE_RINGING: false,
/**
* Whether the speech to text transcription subtitles panel is disabled.
* If {@code undefined}, defaults to {@code false}.
@@ -70,9 +62,6 @@ var interfaceConfig = {
ENABLE_DIAL_OUT: true,
// DEPRECATED. Animation no longer supported.
// ENABLE_FEEDBACK_ANIMATION: false,
FILM_STRIP_MAX_HEIGHT: 120,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
@@ -227,7 +216,7 @@ var interfaceConfig = {
/**
* Specify custom URL for downloading f droid app.
*/
// MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/en/packages/org.jitsi.meet/',
// MOBILE_DOWNLOAD_LINK_F_DROID: 'https://f-droid.org/packages/org.jitsi.meet/',
// Connection indicators (
// CONNECTION_INDICATOR_AUTO_HIDE_ENABLED,

View File

@@ -1,2 +1,6 @@
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
. ${THIS_DIR}/../node_modules/react-native/scripts/find-node-for-xcode.sh
export NODE_BINARY=$(command -v node)
export ENTRY_FILE="${PROJECT_DIR}/../../index.ios.js"

View File

@@ -69,7 +69,7 @@ platform :ios do
end
)
# Inrement the build number by 1
# Increment the build number by 1
increment_build_number(
build_number: Time.now.to_i,
xcodeproj: "app/app.xcodeproj"

View File

@@ -54,7 +54,7 @@ pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework lite/Frameworks/
cp -a ${PROJECT_REPO}/ios/sdk/out/hermes.xcframework lite/Frameworks/
cp -a ${PROJECT_REPO}/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework lite/Frameworks/
# Add all files to git
git add -A .

View File

@@ -54,7 +54,7 @@ pushd ${RELEASE_REPO}
# Put the new files in the repo
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework Frameworks/
cp -a ${PROJECT_REPO}/ios/sdk/out/hermes.xcframework Frameworks/
cp -a ${PROJECT_REPO}/ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermes.xcframework Frameworks/
# Add all files to git
git add -A .

View File

@@ -334,6 +334,7 @@
"kickParticipantButton": "Expulser",
"kickParticipantDialog": "Êtes-vous sûr(e) de vouloir expulser ce participant ?",
"kickParticipantTitle": "Expulser ce participant ?",
"kickSystemTitle": "Oups ! Vous avez été expulsé de la réunion",
"kickTitle": "Oups ! vous avez été expulsé(e) par {{participantDisplayName}}",
"linkMeeting": "Relier la conférence",
"linkMeetingTitle": "Relier la conférence à Salesforce",
@@ -439,7 +440,10 @@
"shareScreenWarningD2": "vous devez arrêter le partage d'audio, démarrer le partage d'écran et cocher l'option \"Partager l'audio\".",
"shareScreenWarningH1": "Si vous voulez partager uniquement votre écran:",
"shareScreenWarningTitle": "Vous devez cesser de partager votre audio avant de partager votre écran",
"shareVideoConfirmPlay": "Vous êtes sur le point d'ouvrir un site web externe. Voulez-vous continuer ?",
"shareVideoConfirmPlayTitle": "{{name}} a partagé une vidéo avec vous.",
"shareVideoLinkError": "Veuillez renseigner un lien de diffusion vidéo fonctionnel.",
"shareVideoLinkStopped": "La vidéo de {{name}} a été arrêtée.",
"shareVideoTitle": "Partager une vidéo",
"shareYourScreen": "Partager votre écran",
"shareYourScreenDisabled": "Le partage d'écran est désactivé.",
@@ -638,6 +642,7 @@
"on": "En direct",
"onBy": "{{name}} a démarré la diffusion en direct",
"pending": "Lancement du direct…",
"policyError": "Vous avez essayé de démarrer une diffusion en direct trop rapidement. Veuillez réessayer plus tard !",
"serviceName": "Service de diffusion en direct",
"sessionAlreadyActive": "Cette session est déjà en cours d'enregistrement ou de diffusion.",
"signIn": "Se connecter avec Google",
@@ -736,6 +741,7 @@
"connectedOneMember": "{{name}} a rejoint la réunion",
"connectedThreePlusMembers": "{{name}} et {{count}} autres personnes ont rejoint la réunion",
"connectedTwoMembers": "{{first}} et {{second}} ont rejoint la réunion",
"connectionFailed": "Connexion échouée. Veuillez réessayer plus tard !",
"dataChannelClosed": "Qualité vidéo dégradée",
"dataChannelClosedDescription": "Le canal de communication avec le Bridge a été interrompu, la qualité vidéo se trouve limitée à sa valeur la plus faible.",
"dataChannelClosedDescriptionWithAudio": "Le canal de pont est fermé, ce qui peut entraîner des perturbations de l'audio et de la vidéo.",
@@ -750,6 +756,9 @@
"gifsMenu": "GIPHY",
"groupTitle": "Notifications",
"hostAskedUnmute": "Le modérateur souhaite vous donner la parole",
"invalidTenant": "Tenant invalide",
"invalidTenantHyphenDescription": "Le tenant que vous utilisez est invalide (commence ou se termine par '-').",
"invalidTenantLengthDescription": "Le tenant que vous utilisez est trop long.",
"invitedOneMember": "{{name}} a été invité(e)",
"invitedThreePlusMembers": "{{name}} et {{count}} autres ont été invités",
"invitedTwoMembers": "{{first}} et {{second}} ont été invités",
@@ -786,6 +795,7 @@
"newDeviceAction": "Utiliser",
"newDeviceAudioTitle": "Nouveau périphérique audio détecté",
"newDeviceCameraTitle": "Nouvelle caméra détectée",
"nextToSpeak": "Vous êtes le prochain à prendre la parole",
"noiseSuppressionDesktopAudioDescription": "La suppression de bruit ne peut pas être activée en même temps que la partage audio du système, veuillez le désactiver et réessayer.",
"noiseSuppressionFailedTitle": "Échec du démarrage de la suppression de bruit",
"noiseSuppressionStereoDescription": "La suppression de bruit dune source stéréo nest pas encore supportée.",
@@ -820,6 +830,7 @@
"videoUnmuteBlockedDescription": "Le rétablissement de la vidéo a été bloqué temporairement en raison de limites système.",
"videoUnmuteBlockedTitle": "Rétablissement de la caméra bloqué !",
"viewLobby": "Voir la salle d'attente",
"viewParticipants": "Voir les participants",
"viewVisitors": "Voir les visiteurs",
"waitingParticipants": "{{waitingParticipants}} personnes",
"waitingVisitors": "Visiteurs en attente dans la file : {{waitingVisitors}}",
@@ -839,6 +850,8 @@
"breakoutRooms": "Salles annexes",
"goLive": "Passer en direct",
"invite": "Inviter quelqu'un",
"lowerAllHands": "Abaisser toutes les mains",
"lowerHand": "Abaisser la main",
"moreModerationActions": "Options de modération supplémentaires",
"moreModerationControls": "Options de modération supplémentaires",
"moreParticipantOptions": "Options supplémentaires pour les participants",
@@ -875,6 +888,7 @@
"submit": "Envoyer"
},
"by": "Par {{ name }}",
"closeButton": "Fermer le sondage",
"create": {
"addOption": "Ajouter une option",
"answerPlaceholder": "Option {{index}}",
@@ -914,9 +928,11 @@
"configuringDevices": "Configuration des appareils…",
"connectedWithAudioQ": "Êtes-vous connecté avec le microphone ?",
"connection": {
"failed": "Le test de connexion a échoué !",
"good": "Votre connexion Internet est bonne !",
"nonOptimal": "Votre connexion n'est pas optimale",
"poor": "Vous avez une mauvaise connexion"
"poor": "Vous avez une mauvaise connexion",
"running": "Exécution du test de connexion…"
},
"connectionDetails": {
"audioClipping": "Attendez vous à ce que votre son soit coupé.",
@@ -925,6 +941,7 @@
"goodQuality": "Impressionnant ! La qualité de vos médias sera excellente",
"noMediaConnectivity": "Nous n'avons pas pu trouver un moyen d'établir une connectivité multimédia pour ce test. Cela est généralement causé par un pare-feu ou un NAT.",
"noVideo": "Attendez vous à ce que votre qualité vidéo soit très mauvaise.",
"testFailed": "Le test de connexion a rencontré des problèmes inattendus, mais cela pourrait ne pas affecter votre expérience.",
"undetectable": "Si vous ne parvenez toujours pas à passer des appels dans le navigateur, nous vous recommandons de vous assurer que vos haut-parleurs, microphone et caméra sont correctement configurés, que vous avez accordé à votre navigateur les droits d'utiliser votre microphone et votre caméra et que la version de votre navigateur est à jour. Si vous rencontrez toujours des difficultés pour appeler, vous devez contacter le développeur de l'application Web.",
"veryPoorConnection": "Attendez vous à ce que la qualité de votre appel soit très mauvaise",
"videoFreezing": "Attendez vous à ce que votre vidéo saute, soit noire, et pixelisée.",
@@ -1040,6 +1057,7 @@
"onBy": "{{name}} a démarré l'enregistrement",
"onlyRecordSelf": "Enregistrer seulement mon audio et ma vidéo.",
"pending": "Préparation de l'enregistrement de la réunion…",
"policyError": "Vous avez essayé de démarrer un enregistrement trop rapidement. Veuillez réessayer plus tard !",
"recordAudioAndVideo": "Enregistrer l'audio et la vidéo",
"recordTranscription": "Enregistrer la transcription",
"saveLocalRecording": "Sauvegarder lenregistrement local (Beta)",
@@ -1088,6 +1106,7 @@
"desktopShareWarning": "Vous devez repartager l'écran pour que ces paramètres soient utilisés.",
"devices": "Périphériques",
"followMe": "Tout le monde me suit",
"followMeRecorder": "L'enregistreur me suit",
"framesPerSecond": "images par seconde",
"incomingMessage": "un message arrive",
"language": "Langue",
@@ -1234,6 +1253,7 @@
"lobbyButton": "Activer / Désactiver le mode salle d'attente",
"localRecording": "Activer / Désactiver les contrôles d'enregistrement local",
"lockRoom": "Activer / Désactiver le mot de passe de la réunion",
"love": "Cœur",
"lowerHand": "Baisser la main",
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
"moreActionsMenu": "Menu d'actions supplémentaires",
@@ -1251,6 +1271,7 @@
"privateMessage": "Envoyer un message privé",
"profile": "Éditer votre profil",
"raiseHand": "Lever la main",
"react": "Réactions aux messages",
"reactions": "Réactions",
"reactionsMenu": "Ouvrir / fermer le menu réactions",
"recording": "Activer / Désactiver l'enregistrement",
@@ -1322,6 +1343,7 @@
"lobbyButtonEnable": "Activer le mode salle d'attente / contrôle des participant(e)s",
"login": "Connexion",
"logout": "Déconnexion",
"love": "Cœur",
"lowerYourHand": "Baisser la main",
"moreActions": "Plus d'actions",
"moreOptions": "Plus d'options",
@@ -1347,6 +1369,7 @@
"raiseYourHand": "Lever la main",
"reactionBoo": "Envoyer réaction huer",
"reactionClap": "Envoyer réaction applaudir",
"reactionHeart": "Envoyer une réaction en forme de cœur",
"reactionLaugh": "Envoyer réaction rire",
"reactionLike": "Envoyer réaction approuver",
"reactionSilence": "Envoyer réaction silence",
@@ -1380,7 +1403,7 @@
"transcribing": {
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
"expandedLabel": "La transcription est actuellement activée",
"failedToStart": "Échec de démarrage de la transcription",
"failed": "La transcription a échoué",
"labelToolTip": "La transcription de la réunion est en cours",
"sourceLanguageDesc": "Actuellement, la langue de la réunion est sélectionnée à <b>{{sourceLanguage}}</b>. <br/> Vous pouvez la changer à partir de ",
"sourceLanguageHere": "ici",
@@ -1481,10 +1504,15 @@
},
"visitors": {
"chatIndicator": "(visiteur)",
"labelTooltip": "Nombre de Visiteurs",
"joinMeeting": {
"description": "Vous êtes actuellement un observateur dans cette conférence.",
"raiseHand": "Levez la main",
"title": "Rejoindre la réunion",
"wishToSpeak": "Si vous souhaitez prendre la parole, veuillez lever la main ci-dessous et attendre l'approbation du modérateur."
},
"labelTooltip": "Nombre de Visiteurs: {{count}}",
"notification": {
"demoteDescription": "Envoyé ici par {{actor}}, levez la main pour participer",
"description": "Pour participer lever la main.",
"noMainParticipantsDescription": "Un participant doit démarrer la réunion. Veuillez réessayer dans un moment.",
"noMainParticipantsTitle": "Cette réunion n'a pas encore commencé.",
"noVisitorLobby": "Vous ne pouvez pas rejoindre tant qu'une salle d'attente est activée pour la réunion.",

View File

@@ -334,6 +334,7 @@
"kickParticipantButton": "Izraidīt",
"kickParticipantDialog": "Vai esat pārliecināti, ka vēlaties izraidīt šo dalībnieku?",
"kickParticipantTitle": "Izraidīt šo dalībnieku?",
"kickSystemTitle": "Ak! Jūs izraidīja no sapulces",
"kickTitle": "Ak! {{participantDisplayName}} izraidīja jūs no sapulces",
"linkMeeting": "Sasaistīt sapulci",
"linkMeetingTitle": "Sasaistīt sapulci ar Salesforce",

View File

@@ -410,6 +410,7 @@
"sendPrivateMessageTitle": "私信回复?",
"serviceUnavailable": "服务不可用",
"sessTerminated": "通话已结束",
"sessTerminatedReason": "会议已经结束",
"sessionRestarted": "由于连接问题,呼叫重新启动。",
"shareAudio": "继续",
"shareAudioTitle": "如何分享音频",

View File

@@ -307,8 +307,8 @@
"contactSupport": "Contact support",
"copied": "Copied",
"copy": "Copy",
"demoteParticipantDialog": "Are you sure you want to move this participant to visitor?",
"demoteParticipantTitle": "Move to visitor",
"demoteParticipantDialog": "Are you sure you want to move this participant to viewer?",
"demoteParticipantTitle": "Move to viewer",
"dismiss": "Dismiss",
"displayNameRequired": "Hi! Whats your name?",
"done": "Done",
@@ -831,9 +831,9 @@
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
"viewLobby": "View lobby",
"viewParticipants": "View participants",
"viewVisitors": "View visitors",
"viewVisitors": "View viewers",
"waitingParticipants": "{{waitingParticipants}} people",
"waitingVisitors": "Visitors waiting in queue: {{waitingVisitors}}",
"waitingVisitors": "Viewers waiting in queue: {{waitingVisitors}}",
"waitingVisitorsTitle": "The meeting is not live yet!",
"whiteboardLimitDescription": "Please save your progress, as the user limit will soon be reached and the whiteboard will close.",
"whiteboardLimitTitle": "Whiteboard usage"
@@ -870,7 +870,7 @@
"participantsList": "Meeting participants ({{count}})",
"visitorInQueue": " (waiting {{count}})",
"visitorRequests": " (requests {{count}})",
"visitors": "Visitors {{count}}",
"visitors": "Viewers {{count}}",
"waitingLobby": "Waiting in lobby ({{count}})"
},
"search": "Search participants",
@@ -1083,7 +1083,7 @@
"about": "You can add a $t(lockRoomPassword) to your meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"aboutReadOnly": "Moderator participants can add a $t(lockRoomPassword) to the meeting. Participants will need to provide the $t(lockRoomPassword) before they are allowed to join the meeting.",
"insecureRoomNameWarningNative": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing your meeting ",
"insecureRoomNameWarningWeb": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing you meeting <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
"insecureRoomNameWarningWeb": "The room name is unsafe. Unwanted participants may join your meeting. {{recommendAction}} Learn more about securing your meeting <a href=\"{{securityUrl}}\" rel=\"security\" target=\"_blank\">here</a>.",
"title": "Security Options",
"unsafeRoomActions": {
"meeting": "Consider securing your meeting using the security button.",
@@ -1183,6 +1183,7 @@
"fearful": "Fearful",
"happy": "Happy",
"hours": "{{count}}h",
"labelTooltip": "Number of participants: {{count}}",
"minutes": "{{count}}m",
"name": "Name",
"neutral": "Neutral",
@@ -1451,7 +1452,7 @@
},
"videothumbnail": {
"connectionInfo": "Connection Info",
"demote": "Move to visitor",
"demote": "Move to viewer",
"domute": "Mute",
"domuteOthers": "Mute everyone else",
"domuteVideo": "Disable camera",
@@ -1503,21 +1504,21 @@
"webAssemblyWarningDescription": "WebAssembly disabled or not supported by this browser"
},
"visitors": {
"chatIndicator": "(visitor)",
"chatIndicator": "(viewer)",
"joinMeeting": {
"description": "You're currently an observer in this conference.",
"description": "You're currently a viewer in this conference.",
"raiseHand": "Raise your hand",
"title": "Joining meeting",
"wishToSpeak": "If you wish to speak, please raise your hand below and wait for the moderator's approval."
},
"labelTooltip": "Number of visitors: {{count}}",
"labelTooltip": "Number of viewers: {{count}}",
"notification": {
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
"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.",
"notAllowedPromotion": "A participant needs to allow your request first.",
"title": "You are a visitor in the meeting"
"title": "You are a viewer in the meeting"
},
"waitingMessage": "You'll join the meeting as soon as it is live!"
},

View File

@@ -115,7 +115,7 @@ import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
import { SETTINGS_TABS } from '../../react/features/settings/constants';
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions';
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/functions';
@@ -590,7 +590,7 @@ function initCommands() {
* @param { string } arg.title - Notification title.
* @param { string } arg.description - Notification description.
* @param { string } arg.uid - Optional unique identifier for the notification.
* @param { string } arg.type - Notification type, either `error`, `info`, `normal`, `success` or `warning`.
* @param { string } arg.type - Notification type, either `error`, `normal`, `success` or `warning`.
* Defaults to "normal" if not provided.
* @param { string } arg.timeout - Timeout type, either `short`, `medium`, `long` or `sticky`.
* Defaults to "short" if not provided.
@@ -1571,8 +1571,8 @@ class API {
formattedArgument += `${arg.toString()}: ${arg.stack}`;
} else if (typeof arg === 'object') {
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
// do something better for parsing the arguments. But since this option for strigify is part of the
// JSON.stringify. For example Map instance will be translated to '{}'. So I think we have to eventually
// do something better for parsing the arguments. But since this option for stringify is part of the
// public interface and I think it could be useful in some cases I will it for now.
try {
formattedArgument += JSON.stringify(arg);

View File

@@ -194,80 +194,6 @@ function changeParticipantNumber(APIInstance, number) {
APIInstance._numberOfParticipants += number;
}
/**
* Generates the URL for the iframe.
*
* @param {string} domain - The domain name of the server that hosts the
* conference.
* @param {string} [options] - Another optional parameters.
* @param {Object} [options.configOverwrite] - Object containing configuration
* options defined in config.js to be overridden.
* @param {Object} [options.interfaceConfigOverwrite] - Object containing
* configuration options defined in interface_config.js to be overridden.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @param {string} [options.lang] - The meeting's default language.
* @param {string} [options.roomName] - The name of the room to join.
* @returns {string} The URL.
*/
function generateURL(domain, options = {}) {
return urlObjectToString({
...options,
url: `https://${domain}/#jitsi_meet_external_api_id=${id}`
});
}
/**
* Parses the arguments passed to the constructor. If the old format is used
* the function translates the arguments to the new format.
*
* @param {Array} args - The arguments to be parsed.
* @returns {Object} JS object with properties.
*/
function parseArguments(args) {
if (!args.length) {
return {};
}
const firstArg = args[0];
switch (typeof firstArg) {
case 'string': // old arguments format
case 'undefined': {
// Not sure which format but we are trying to parse the old
// format because if the new format is used everything will be undefined
// anyway.
const [
roomName,
width,
height,
parentNode,
configOverwrite,
interfaceConfigOverwrite,
jwt,
onload,
lang
] = args;
return {
roomName,
width,
height,
parentNode,
configOverwrite,
interfaceConfigOverwrite,
jwt,
onload,
lang
};
}
case 'object': // new arguments format
return args[0];
default:
throw new Error('Can\'t parse the arguments!');
}
}
/**
* Compute valid values for height and width. If a number is specified it's
* treated as pixel units. If the value is expressed in px, em, pt or
@@ -336,7 +262,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* @param {string} [options.release] - The key used for specifying release if enabled on the backend.
* @param {string} [options.sandbox] - Sandbox directive for the created iframe, if desired.
*/
constructor(domain, ...args) {
constructor(domain, options = {}) {
super();
const {
roomName = '',
@@ -345,21 +271,22 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
parentNode = document.body,
configOverwrite = {},
interfaceConfigOverwrite = {},
jwt = undefined,
lang = undefined,
onload = undefined,
jwt,
lang,
onload,
invitees,
iceServers,
devices,
userInfo,
e2eeKey,
release,
sandbox = ''
} = parseArguments(args);
sandbox
} = options;
const localStorageContent = jitsiLocalStorage.getItem('jitsiLocalStorage');
this._parentNode = parentNode;
this._url = generateURL(domain, {
this._url = urlObjectToString({
configOverwrite,
iceServers,
interfaceConfigOverwrite,
@@ -371,7 +298,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
appData: {
localStorageContent
},
release
release,
url: `https://${domain}/#jitsi_meet_external_api_id=${id}`
});
this._createIFrame(height, width, sandbox);
@@ -1313,6 +1241,10 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* Returns the state of availability electron share screen via external api.
*
* @returns {Promise}
*
* TODO: should be removed after we make sure that all Electron clients use only versions
* after with the legacy SS suport was removed from the electron SDK. If we remove it now the SS for Electron
* clients with older versions wont work.
*/
_isNewElectronScreensharingSupported() {
return this._transport.sendRequest({

4081
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,7 @@
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.19/jitsi-excalidraw-0.0.19.tgz",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rnnoise-wasm": "0.1.0",
"@jitsi/rnnoise-wasm": "0.2.0",
"@jitsi/rtcstats": "9.5.1",
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
"@microsoft/microsoft-graph-client": "3.0.1",
@@ -68,7 +68,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1890.0.0+144b0cab/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1901.0.0+22daa357/lib-jitsi-meet.tgz",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -76,6 +76,7 @@
"optional-require": "1.0.3",
"pixelmatch": "5.3.0",
"promise.allsettled": "1.0.4",
"promise.withresolvers": "1.0.3",
"punycode": "2.3.0",
"react": "18.2.0",
"react-dom": "18.2.0",
@@ -134,14 +135,16 @@
"@babel/plugin-transform-private-methods": "7.25.9",
"@babel/preset-env": "7.25.9",
"@babel/preset-react": "7.25.9",
"@jitsi/eslint-config": "4.1.10",
"@jitsi/eslint-config": "5.0.9",
"@react-native/metro-config": "0.75.4",
"@stylistic/eslint-plugin": "2.12.1",
"@types/amplitude-js": "8.16.5",
"@types/audioworklet": "0.0.29",
"@types/dom-screen-wake-lock": "1.0.1",
"@types/js-md5": "0.4.3",
"@types/jsonwebtoken": "9.0.7",
"@types/lodash-es": "4.17.12",
"@types/minimatch": "5.1.2",
"@types/mocha": "10.0.10",
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
@@ -160,8 +163,8 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@typescript-eslint/eslint-plugin": "5.59.5",
"@typescript-eslint/parser": "5.59.5",
"@typescript-eslint/eslint-plugin": "8.19.1",
"@typescript-eslint/parser": "8.19.1",
"@wdio/allure-reporter": "9.4.3",
"@wdio/cli": "9.4.3",
"@wdio/globals": "9.4.3",
@@ -173,12 +176,12 @@
"circular-dependency-plugin": "5.2.0",
"clean-css-cli": "4.3.0",
"css-loader": "6.8.1",
"eslint": "8.40.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsdoc": "46.2.6",
"eslint-plugin-react": "7.32.2",
"eslint-plugin-react-native": "4.0.0",
"eslint-plugin-typescript-sort-keys": "2.3.0",
"eslint": "8.57.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsdoc": "50.6.1",
"eslint-plugin-react": "7.37.3",
"eslint-plugin-react-native": "5.0.0",
"eslint-plugin-typescript-sort-keys": "3.3.0",
"jetifier": "1.6.4",
"jsonwebtoken": "9.0.2",
"metro-react-native-babel-preset": "0.77.0",
@@ -220,9 +223,11 @@
"tsc-test:native": "tsc --project tsconfig.native.json --listFilesOnly | grep -v node_modules | grep web",
"start": "make dev",
"test": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.conf.ts",
"test-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.conf.ts --spec",
"test-ff": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.firefox.conf.ts",
"test-dev": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.dev.conf.ts",
"test-grid": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts"
"test-grid": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts",
"test-grid-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.grid.conf.ts --spec"
},
"resolutions": {
"@types/react": "17.0.14",

View File

@@ -69,7 +69,7 @@ cd ios && pod install && cd ..
## Android
- In your build.gradle have at least `minSdkVersion = 24`
- In your build.gradle have at least `minSdkVersion = 26`
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
```xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />

View File

@@ -0,0 +1,117 @@
package org.jitsi.meet.sdk;
import static android.Manifest.permission.POST_NOTIFICATIONS;
import static android.Manifest.permission.RECORD_AUDIO;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.facebook.react.ReactActivity;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.PermissionListener;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class implements a ReactModule and it's
* responsible for launching/aborting a service when a conference is in progress.
*/
@ReactModule(name = JMOngoingConferenceModule.NAME)
class JMOngoingConferenceModule extends ReactContextBaseJavaModule {
public static final String NAME = "JMOngoingConference";
private static final int PERMISSIONS_REQUEST_CODE = (int) (Math.random() * Short.MAX_VALUE);
public JMOngoingConferenceModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void launch() {
Context context = getReactApplicationContext();
ReactActivity reactActivity = (ReactActivity) getCurrentActivity();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
JMOngoingConferenceService.launch(context);
JitsiMeetLogger.i(NAME + " launch");
return;
}
PermissionListener listener = new PermissionListener() {
@Override
public boolean onRequestPermissionsResult(int i, String[] strings, int[] results) {
JitsiMeetLogger.i(NAME + " Permission callback received");
if (results == null || results.length == 0) {
JitsiMeetLogger.w(NAME + " Permission results are null or empty");
return true;
}
int counter = 0;
for (int result : results) {
if (result == PackageManager.PERMISSION_GRANTED) {
counter++;
}
}
JitsiMeetLogger.i(NAME + " Permissions granted: " + counter + "/" + results.length);
if (counter == results.length) {
JitsiMeetLogger.i(NAME + " All permissions granted, launching service");
JMOngoingConferenceService.launch(context);
} else {
JitsiMeetLogger.w(NAME + " Not all permissions were granted");
}
return true;
}
};
JitsiMeetLogger.i(NAME + " Checking Tiramisu permissions");
List<String> permissionsList = new ArrayList<>();
permissionsList.add(POST_NOTIFICATIONS);
permissionsList.add(RECORD_AUDIO);
String[] permissionsArray = new String[ permissionsList.size() ];
permissionsArray = permissionsList.toArray( permissionsArray );
try {
JitsiMeetLogger.i(NAME + " Requesting permissions: " + Arrays.toString(permissionsArray));
reactActivity.requestPermissions(permissionsArray, PERMISSIONS_REQUEST_CODE, listener);
} catch (Exception e) {
JitsiMeetLogger.e(e, NAME + " Error requesting permissions");
}
}
@ReactMethod
public void abort() {
Context context = getReactApplicationContext();
JMOngoingConferenceService.abort(context);
JitsiMeetLogger.i(NAME + " abort");
}
@NonNull
@Override
public String getName() {
return NAME;
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* This class implements an Android {@link Service}, a foreground one specifically, and it's
* responsible for presenting an ongoing notification when a conference is in progress.
* The service will help keep the app running while in the background.
*
* See: https://developer.android.com/guide/components/services
*/
public class JMOngoingConferenceService extends Service {
private static final String TAG = JMOngoingConferenceService.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
public static void launch(Context context) {
try {
RNOngoingNotification.createOngoingConferenceNotificationChannel(context);
JitsiMeetLogger.i(TAG + " Notification channel creation completed");
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error creating notification channel");
}
Intent intent = new Intent(context, JMOngoingConferenceService.class);
try {
context.startForegroundService(intent);
JitsiMeetLogger.i(TAG + " Starting foreground service");
} catch (RuntimeException e) {
// Avoid crashing due to ForegroundServiceStartNotAllowedException (API level 31).
// See: https://developer.android.com/guide/components/foreground-services#background-start-restrictions
JitsiMeetLogger.w(TAG + " Ongoing conference service not started", e);
}
}
public static void abort(Context context) {
Intent intent = new Intent(context, JMOngoingConferenceService.class);
context.stopService(intent);
}
@Override
public void onCreate() {
super.onCreate();
JitsiMeetLogger.i(TAG + " onCreate called");
try {
Notification notification = RNOngoingNotification.buildOngoingConferenceNotification(this);
JitsiMeetLogger.i(TAG + " Notification build result: " + (notification != null));
if (notification == null) {
stopSelf();
JitsiMeetLogger.w(TAG + " Couldn't start service, notification is null");
} else {
JitsiMeetLogger.i(TAG + " Starting service in foreground with notification");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK | ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
} catch (Exception e) {
JitsiMeetLogger.e(e, TAG + " Error in onCreate");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY;
}
}

View File

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

View File

@@ -0,0 +1,108 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JMOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final String RN_ONGOING_CONFERENCE_CHANNEL_ID = "OngoingConferenceChannel";
static void createOngoingConferenceNotificationChannel(Context context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Service.NOTIFICATION_SERVICE);
NotificationChannel channel = notificationManager.getNotificationChannel(RN_ONGOING_CONFERENCE_CHANNEL_ID);
if (channel != null) {
JitsiMeetLogger.i(TAG + " Notification channel already exists");
return;
}
channel = new NotificationChannel(
RN_ONGOING_CONFERENCE_CHANNEL_ID,
context.getString(R.string.ongoing_notification_channel_name),
NotificationManager.IMPORTANCE_DEFAULT
);
channel.enableVibration(true);
channel.setShowBadge(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
notificationManager.createNotificationChannel(channel);
JitsiMeetLogger.i(TAG + " Notification channel created with importance: " + channel.getImportance());
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
JitsiMeetLogger.i(TAG + " Creating notification with context: " + context);
// Creating an intent to launch app's main activity
Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
assert intent != null;
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Creating PendingIntent
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, RN_ONGOING_CONFERENCE_CHANNEL_ID);
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()))
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setContentIntent(pendingIntent);
return builder.build();
}
}

View File

@@ -36,6 +36,7 @@
"moment-duration-format": "0.0.0",
"optional-require": "0.0.0",
"promise.allsettled": "0.0.0",
"promise.withresolvers": "0.0.0",
"punycode": "0.0.0",
"react-emoji-render": "0.0.0",
"react-i18next": "0.0.0",

View File

@@ -14,24 +14,14 @@ module.exports = {
project: [ './tsconfig.web.json', './tsconfig.native.json' ]
},
rules: {
'@typescript-eslint/naming-convention': [
'error',
{
'selector': 'interface',
'format': [ 'PascalCase' ],
'custom': {
'regex': '^I[A-Z]',
'match': true
}
}
]
// TODO: Remove these and fix the warnings
'@typescript-eslint/no-unsafe-function-type': 0,
'@typescript-eslint/no-wrapper-object-types': 0,
'@typescript-eslint/no-require-imports': 0
}
}
],
'rules': {
// XXX remove this eventually.
'react/jsx-indent-props': 0
},
'settings': {
'react': {
'version': 'detect'

View File

@@ -4,8 +4,10 @@ import ReactDOM from 'react-dom';
import AlwaysOnTop from './AlwaysOnTop';
// Render the main/root Component.
/* eslint-disable-next-line react/no-deprecated */
ReactDOM.render(<AlwaysOnTop />, document.getElementById('react'));
window.addEventListener(
'beforeunload',
/* eslint-disable-next-line react/no-deprecated */
() => ReactDOM.unmountComponentAtNode(document.getElementById('react') ?? document.body));

View File

@@ -311,7 +311,7 @@ export function createInviteDialogEvent(
* @returns {Object}
*/
export function createNetworkInfoEvent({ isOnline, networkType, details }:
{ details?: Object; isOnline: boolean; networkType?: string; }) {
{ details?: Object; isOnline: boolean; networkType?: string; }) {
const attributes: {
details?: Object;
isOnline: boolean;

View File

@@ -154,6 +154,30 @@ export async function createHandlers({ getState }: IStore) {
return handlers;
}
/**
* Checks whether a url is a data URL or not.
*
* @param {string} url - The URL to be checked.
* @returns {boolean}
*/
function isDataURL(url?: string): boolean {
if (typeof url !== 'string') { // The icon will be ignored
return false;
}
try {
const urlObject = new URL(url);
if (urlObject.protocol === 'data:') {
return false;
}
} catch {
return false;
}
return true;
}
/**
* Inits JitsiMeetJS.analytics by setting permanent properties and setting the handlers from the loaded scripts.
* NOTE: Has to be used after JitsiMeetJS.init. Otherwise analytics will be null.
@@ -185,13 +209,21 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
inIframe?: boolean;
isPromotedFromVisitor?: boolean;
isVisitor?: boolean;
overwritesCustomButtonsWithURL?: boolean;
overwritesCustomParticipantButtonsWithURL?: boolean;
overwritesDefaultLogoUrl?: boolean;
overwritesDeploymentUrls?: boolean;
overwritesEtherpadBase?: boolean;
overwritesHosts?: boolean;
overwritesIceServers?: boolean;
overwritesLiveStreamingUrls?: boolean;
overwritesPeopleSearchUrl?: boolean;
overwritesPrejoinConfigICEUrl?: boolean;
overwritesSalesforceUrl?: boolean;
overwritesSupportUrl?: boolean;
overwritesWatchRTCConfigParams?: boolean;
overwritesWatchRTCProxyUrl?: boolean;
overwritesWatchRTCWSUrl?: boolean;
server?: string;
tenant?: string;
wasLobbyVisible?: boolean;
@@ -235,6 +267,21 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
permanentProperties.overwritesSalesforceUrl = 'config.salesforceUrl' in params;
permanentProperties.overwritesPeopleSearchUrl = 'config.peopleSearchUrl' in params;
permanentProperties.overwritesDefaultLogoUrl = 'config.defaultLogoUrl' in params;
permanentProperties.overwritesEtherpadBase = 'config.etherpad_base' in params;
const hosts = params['config.hosts'] ?? {};
const hostsProps = [ 'anonymousdomain', 'authdomain', 'domain', 'focus', 'muc', 'visitorFocus' ];
permanentProperties.overwritesHosts = 'config.hosts' in params
|| Boolean(hostsProps.find(p => `config.hosts.${p}` in params || (typeof hosts === 'object' && p in hosts)));
permanentProperties.overwritesWatchRTCConfigParams = 'config.watchRTCConfigParams' in params;
const watchRTCConfigParams = params['config.watchRTCConfigParams'] ?? {};
permanentProperties.overwritesWatchRTCProxyUrl = ('config.watchRTCConfigParams.proxyUrl' in params)
|| (typeof watchRTCConfigParams === 'object' && 'proxyUrl' in watchRTCConfigParams);
permanentProperties.overwritesWatchRTCWSUrl = ('config.watchRTCConfigParams.wsUrl' in params)
|| (typeof watchRTCConfigParams === 'object' && 'wsUrl' in watchRTCConfigParams);
const prejoinConfig = params['config.prejoinConfig'] ?? {};
permanentProperties.overwritesPrejoinConfigICEUrl = ('config.prejoinConfig.preCallTestICEUrl' in params)
@@ -260,6 +307,18 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
)
);
permanentProperties.overwritesIceServers = Boolean(Object.keys(params).find(k => k.startsWith('iceServers')));
const customToolbarButtons = params['config.customToolbarButtons'] ?? [];
permanentProperties.overwritesCustomButtonsWithURL = Boolean(
customToolbarButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
const customParticipantMenuButtons = params['config.customParticipantMenuButtons'] ?? [];
permanentProperties.overwritesCustomParticipantButtonsWithURL = Boolean(
customParticipantMenuButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
if (deploymentInfo) {

View File

@@ -158,4 +158,3 @@ export function maybeRedirectToTokenAuthUrl(
return false;
}

View File

@@ -27,11 +27,9 @@ export interface IProps {
*/
export class AbstractApp<P extends IProps = IProps> extends BaseApp<P> {
/**
* The deferred for the initialisation {{promise, resolve, reject}}.
* The deferred for the initialization {{promise, resolve, reject}}.
*/
_init: {
promise: Promise<any>;
};
_init: PromiseWithResolvers<any>;
/**
* Initializes the app.

View File

@@ -140,10 +140,7 @@ function _getWebWelcomePageRoute(state: IReduxState) {
*
* @returns {Object}
*/
function _getEmptyRoute(): {
component: React.ReactNode;
href?: string;
} {
function _getEmptyRoute(): { component: React.ReactNode; href?: string; } {
return {
component: BlankPage,
href: undefined

View File

@@ -113,8 +113,7 @@ function _isMaybeSplitBrainError(getState: IStore['getState'], action: AnyAction
const { error } = action;
const isShardChangedError = error
&& error.message === 'item-not-found'
&& error.details
&& error.details.shard_changed;
&& error.details?.shard_changed;
if (isShardChangedError) {
const state = getState();

View File

@@ -121,7 +121,7 @@ MiddlewareRegistry.register(store => next => action => {
if (isTokenAuthEnabled(config)
&& config.tokenAuthUrlAutoRedirect
&& state['features/base/jwt'].jwt) {
// auto redirect is turned on and we have succesfully logged in
// auto redirect is turned on and we have successfully logged in
// let's mark that
dispatch(setTokenAuthUrlSuccess(true));
}

View File

@@ -14,7 +14,6 @@ import PersistenceRegistry from '../../redux/PersistenceRegistry';
import ReducerRegistry from '../../redux/ReducerRegistry';
import StateListenerRegistry from '../../redux/StateListenerRegistry';
import SoundCollection from '../../sounds/components/SoundCollection';
import { createDeferred } from '../../util/helpers';
import { appWillMount, appWillUnmount } from '../actions';
import logger from '../logger';
@@ -46,9 +45,7 @@ export default class BaseApp<P> extends Component<P, IState> {
/**
* The deferred for the initialisation {{promise, resolve, reject}}.
*/
_init: {
promise: Promise<any>;
};
_init: PromiseWithResolvers<any>;
/**
* Initializes a new {@code BaseApp} instance.
@@ -79,7 +76,7 @@ export default class BaseApp<P> extends Component<P, IState> {
* @see {@link #_initStorage}
* @type {Promise}
*/
this._init = createDeferred<void>();
this._init = Promise.withResolvers();
try {
await this._initStorage();

View File

@@ -248,8 +248,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
}
!error.recoverable
&& conference
&& conference.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
&& conference?.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
// Even though we don't care too much about the failure, it may be
// good to know that it happen, so log it (on the info level).
logger.info('JitsiConference.leave() rejected with:', reason);
@@ -430,7 +429,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio
} as INotificationProps;
const { locationURL = { href: '' } as URL } = getState()['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const { tenant = '' } = parseURIString(locationURL.href) || {};
if (tenant.startsWith('-') || tenant.endsWith('-')) {
notificationProps.descriptionKey = 'notify.invalidTenantHyphenDescription';
@@ -551,7 +550,7 @@ function _pinParticipant({ getState }: IStore, next: Function, action: AnyAction
const actionName = id ? ACTION_PINNED : ACTION_UNPINNED;
const local
= participantById?.local
|| (!id && pinnedParticipant && pinnedParticipant.local);
|| (!id && pinnedParticipant?.local);
let participantIdForEvent;
if (local) {

View File

@@ -495,7 +495,7 @@ function _conferenceJoined(state: IConferenceState, { conference }: { conference
* reduction of the specified action.
*/
function _conferenceLeftOrWillLeave(state: IConferenceState, { conference, type }:
{ conference: IJitsiConference; type: string; }) {
{ conference: IJitsiConference; type: string; }) {
const nextState = { ...state };
// The redux action CONFERENCE_LEFT is the last time that we should be

View File

@@ -1,4 +1,6 @@
import { ToolbarButton } from '../../toolbox/types';
import { ILoggingConfig } from '../logging/types';
import { DesktopSharingSourceType } from '../tracks/types';
type ButtonsWithNotifyClick = 'camera' |
'chat' |
@@ -215,6 +217,10 @@ export interface IConfig {
hideAutoAssignButton?: boolean;
hideJoinRoomButton?: boolean;
};
bridgeChannel?: {
ignoreDomain?: string;
preferSctp?: boolean;
};
buttonsWithNotifyClick?: Array<ButtonsWithNotifyClick | {
key: ButtonsWithNotifyClick;
preventExecution: boolean;
@@ -275,11 +281,15 @@ export interface IConfig {
max?: number;
min?: number;
};
desktopSharingSources?: Array<DesktopSharingSourceType>;
dialInConfCodeUrl?: string;
dialInNumbersUrl?: string;
dialOutAuthUrl?: string;
dialOutRegionUrl?: string;
disable1On1Mode?: boolean | null;
disableAEC?: boolean;
disableAGC?: boolean;
disableAP?: boolean;
disableAddingBackgroundImages?: boolean;
disableAudioLevels?: boolean;
disableBeforeUnloadHandlers?: boolean;
@@ -294,11 +304,13 @@ export interface IConfig {
disableJoinLeaveSounds?: boolean;
disableLocalVideoFlip?: boolean;
disableModeratorIndicator?: boolean;
disableNS?: boolean;
disablePolls?: boolean;
disableProfile?: boolean;
disableReactions?: boolean;
disableReactionsModeration?: boolean;
disableRecordAudioNotification?: boolean;
disableRemoteControl?: boolean;
disableRemoteMute?: boolean;
disableRemoveRaisedHandOnFocus?: boolean;
disableResponsiveTiles?: boolean;
@@ -316,6 +328,7 @@ export interface IConfig {
disableVirtualBackground?: boolean;
disabledNotifications?: Array<string>;
disabledSounds?: Array<Sounds>;
displayJids?: boolean;
doNotFlipLocalVideo?: boolean;
doNotStoreRoom?: boolean;
dropbox?: {
@@ -345,7 +358,6 @@ export interface IConfig {
maxMessagesPerSecond?: number;
numRequests?: number;
};
enableAutomaticUrlCopy?: boolean;
enableCalendarIntegration?: boolean;
enableClosePage?: boolean;
enableDisplayNameInStats?: boolean;
@@ -359,6 +371,7 @@ export interface IConfig {
enableOpusRed?: boolean;
enableRemb?: boolean;
enableSaveLogs?: boolean;
enableTalkWhileMuted?: boolean;
enableTcc?: boolean;
enableWebHIDFeature?: boolean;
enableWelcomePage?: boolean;
@@ -372,7 +385,6 @@ export interface IConfig {
faceCenteringThreshold?: number;
};
feedbackPercentage?: number;
fileRecordingsEnabled?: boolean;
fileRecordingsServiceEnabled?: boolean;
fileRecordingsServiceSharingEnabled?: boolean;
filmstrip?: {
@@ -382,11 +394,11 @@ export interface IConfig {
disabled?: boolean;
minParticipantCountForTopPanel?: number;
};
firefox_fake_device?: string;
flags?: {
ssrcRewritingEnabled: boolean;
};
focusUserJid?: string;
forceTurnRelay?: boolean;
gatherStats?: boolean;
giphy?: {
displayMode?: 'all' | 'tile' | 'chat';
@@ -426,6 +438,7 @@ export interface IConfig {
};
iAmRecorder?: boolean;
iAmSipGateway?: boolean;
ignoreStartMuted?: boolean;
inviteAppName?: string | null;
inviteServiceCallFlowsUrl?: string;
inviteServiceUrl?: string;
@@ -458,6 +471,7 @@ export interface IConfig {
};
localSubject?: string;
locationURL?: URL;
logging?: ILoggingConfig;
mainToolbarButtons?: Array<Array<string>>;
maxFullResolutionParticipants?: number;
microsoftApiApplicationClientID?: string;
@@ -479,6 +493,7 @@ export interface IConfig {
enabled?: boolean;
iceTransportPolicy?: string;
mobileCodecPreferenceOrder?: Array<string>;
mobileScreenshareCodec?: string;
stunServers?: Array<{ urls: string; }>;
};
participantMenuButtonsWithNotifyClick?: Array<string | ParticipantMenuButtonsWithNotifyClick | {
@@ -571,7 +586,9 @@ export interface IConfig {
subject?: string;
testing?: {
assumeBandwidth?: boolean;
debugAudioLevels?: boolean;
dumpTranscript?: boolean;
failICE?: boolean;
noAutoPlayVideo?: boolean;
p2pTestMode?: boolean;
skipInterimTranscriptions?: boolean;
@@ -619,6 +636,13 @@ export interface IConfig {
mobileCodecPreferenceOrder?: Array<string>;
persist?: boolean;
};
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
queueService: string;
};
watchRTCConfigParams?: IWatchRTCConfiguration;
webhookProxyUrl?: string;
webrtcIceTcpDisable?: boolean;

View File

@@ -22,6 +22,7 @@ export default [
'apiLogLevels',
'avgRtpStatsN',
'backgroundAlpha',
'brandingRoomAlias',
'breakoutRooms',
'bridgeChannel',
'buttonsWithNotifyClick',
@@ -76,10 +77,6 @@ export default [
'channelLastN',
'connectionIndicators',
'constraints',
'customToolbarButtons',
'brandingRoomAlias',
'debug',
'debugAudioLevels',
'deeplinking.disabled',
'deeplinking.desktop.enabled',
'defaultLocalDisplayName',
@@ -99,7 +96,6 @@ export default [
'disabledSounds',
'disableFilmstripAutohiding',
'disableInitialGUM',
'disableHPF',
'disableInviteFunctions',
'disableIncomingMessageSound',
'disableJoinLeaveSounds',
@@ -148,14 +144,9 @@ export default [
'enableNoAudioDetection',
'enableNoisyMicDetection',
'enableTcc',
'enableAutomaticUrlCopy',
'etherpad_base',
'faceLandmarks',
'failICE',
'feedbackPercentage',
'fileRecordingsEnabled',
'filmstrip',
'firefox_fake_device',
'flags',
'forceTurnRelay',
'gatherStats',
@@ -172,7 +163,6 @@ export default [
'hideAddRoomButton',
'hideEmailInSettings',
'hideLobbyButton',
'hosts',
'iAmRecorder',
'iAmSipGateway',
'ignoreStartMuted',
@@ -195,6 +185,7 @@ export default [
'p2p.enabled',
'p2p.iceTransportPolicy',
'p2p.mobileCodecPreferenceOrder',
'p2p.mobileScreenshareCodec',
'participantMenuButtonsWithNotifyClick',
'participantsPane',
'pcStatsInterval',
@@ -235,8 +226,18 @@ export default [
'useHostPageLocalStorage',
'useTurnUdp',
'videoQuality',
'visitors',
'watchRTCConfigParams',
'visitors.enableMediaOnPromote',
'watchRTCConfigParams.allowBrowserLogCollection',
'watchRTCConfigParams.collectionInterval',
'watchRTCConfigParams.console',
'watchRTCConfigParams.debug',
'watchRTCConfigParams.keys',
'watchRTCConfigParams.logGetStats',
'watchRTCConfigParams.rtcApiKey',
'watchRTCConfigParams.rtcPeerId',
'watchRTCConfigParams.rtcRoomId',
'watchRTCConfigParams.rtcTags',
'watchRTCConfigParams.rtcToken',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',
'whiteboard.enabled'

View File

@@ -89,7 +89,7 @@ export function _setDeeplinkingDefaults(deeplinking: IDeeplinkingConfig) {
android.downloadLink = android.downloadLink
|| 'https://play.google.com/store/apps/details?id=org.jitsi.meet';
android.appPackage = android.appPackage || 'org.jitsi.meet';
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/en/packages/org.jitsi.meet/';
android.fDroidUrl = android.fDroidUrl || 'https://f-droid.org/packages/org.jitsi.meet/';
if (android.dynamicLink) {
android.dynamicLink.apn = android.dynamicLink.apn || 'org.jitsi.meet';
android.dynamicLink.appCode = android.dynamicLink.appCode || 'w2atb';

View File

@@ -22,15 +22,12 @@ export default [
'DISABLE_DOMINANT_SPEAKER_INDICATOR',
'DISABLE_FOCUS_INDICATOR',
'DISABLE_PRIVATE_MESSAGES',
'DISABLE_RINGING',
'DISABLE_TRANSCRIPTION_SUBTITLES',
'DISABLE_VIDEO_BACKGROUND',
'DISPLAY_WELCOME_PAGE_CONTENT',
'ENABLE_DIAL_OUT',
'ENABLE_FEEDBACK_ANIMATION',
'FILM_STRIP_MAX_HEIGHT',
'GENERATE_ROOMNAMES_ON_WELCOME_PAGE',
'HIDE_INVITE_MORE_HEADER',
'INDICATOR_FONT_SIZES',
'INITIAL_TOOLBAR_TIMEOUT',
'LANG_DETECTION',

View File

@@ -63,7 +63,6 @@ export interface IConfigState extends IConfig {
analysis?: {
obfuscateRoomName?: boolean;
};
disableRemoteControl?: boolean;
error?: Error;
oldConfig?: {
bosh?: string;
@@ -75,13 +74,6 @@ export interface IConfigState extends IConfig {
p2p?: object;
websocket?: string;
};
visitors?: {
enableMediaOnPromote?: {
audio?: boolean;
video?: boolean;
};
queueService: string;
};
}
ReducerRegistry.register<IConfigState>('features/base/config', (state = _getInitialState(), action): IConfigState => {

View File

@@ -1,8 +1,11 @@
/**
* Prefixes of devices that will be filtered from the device list.
*
* NOTE: Currently we filter only 'Microsoft Teams Audio Device' virtual device. It seems that it can't be set
* NOTE: It seems that the filtered devices can't be set
* as default device on the OS level and this use case is not handled in the code. If we add more device prefixes that
* can be default devices we should make sure to handle the default device use case.
*/
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [ 'Microsoft Teams Audio Device' ];
export const DEVICE_LABEL_PREFIXES_TO_IGNORE = [
'Microsoft Teams Audio Device',
'ZoomAudioDevice'
];

View File

@@ -9,6 +9,7 @@ import {
OPEN_SHEET
} from './actionTypes';
import { isDialogOpen } from './functions';
import logger from './logger';
/**
* Signals Dialog to close its dialog.
@@ -23,6 +24,8 @@ import { isDialogOpen } from './functions';
* }}
*/
export function hideDialog(component?: ComponentType<any>) {
logger.info(`Hide dialog: ${getComponentDisplayName(component)}`);
return {
type: HIDE_DIALOG,
component
@@ -55,6 +58,8 @@ export function hideSheet() {
* }}
*/
export function openDialog(component: ComponentType<any>, componentProps?: Object) {
logger.info(`Open dialog: ${getComponentDisplayName(component)}`);
return {
type: OPEN_DIALOG,
component,
@@ -101,3 +106,21 @@ export function toggleDialog(component: ComponentType<any>, componentProps?: Obj
}
};
}
/**
* Extracts a printable name for a dialog component.
*
* @param {Object} component - The component to extract the name for.
*
* @returns {string} The display name.
*/
function getComponentDisplayName(component?: ComponentType<any>) {
if (!component) {
return '';
}
const name = component.displayName ?? component.name ?? 'Component';
return name.replace('withI18nextTranslation(Connect(', '') // dialogs with translations
.replace('))', ''); // dialogs with translations suffix
}

View File

@@ -23,7 +23,7 @@ MiddlewareRegistry.register(store => next => action => {
? action.value
: store.getState()['features/dynamic-branding'];
if (language && labels && labels[language]) {
if (language && labels?.[language]) {
changeLanguageBundle(language, labels[language])
.catch(err => {
logger.log('Error setting dynamic language bundle', err);

View File

@@ -237,7 +237,7 @@ function _undoOverwriteLocalParticipant(
* }}
*/
function _user2participant({ avatar, avatarUrl, email, id, name, 'hidden-from-recorder': hiddenFromRecorder }:
{ avatar?: string; avatarUrl?: string; email: string; 'hidden-from-recorder': string | boolean;
{ avatar?: string; avatarUrl?: string; email: string; 'hidden-from-recorder': string | boolean;
id: string; name: string; }) {
const participant: {
avatarURL?: string;

View File

@@ -25,10 +25,6 @@ export function createLocalTrack(type: string, deviceId: string | null, timeout?
JitsiMeetJS.createLocalTracks({
cameraDeviceId: deviceId,
devices: [ type ],
// eslint-disable-next-line camelcase
firefox_fake_device:
window.config?.firefox_fake_device,
micDeviceId: deviceId,
timeout,
...additionalOptions

View File

@@ -5,10 +5,11 @@ import ReducerRegistry from '../redux/ReducerRegistry';
import { equals, set } from '../redux/functions';
import { SET_LOGGING_CONFIG, SET_LOG_COLLECTOR } from './actionTypes';
import { ILoggingConfig, LogLevel } from './types';
const DEFAULT_LOGGING_CONFIG = {
const DEFAULT_LOGGING_CONFIG: ILoggingConfig = {
// default log level for the app and lib-jitsi-meet
defaultLogLevel: 'trace' as LogLevel,
defaultLogLevel: 'trace',
// Option to disable LogCollector (which stores the logs)
// disableLogCollector: true,
@@ -16,8 +17,8 @@ const DEFAULT_LOGGING_CONFIG = {
loggers: {
// The following are too verbose in their logging with the
// {@link #defaultLogLevel}:
'modules/RTC/TraceablePeerConnection.js': 'info' as LogLevel,
'modules/xmpp/strophe.util.js': 'log' as LogLevel
'modules/RTC/TraceablePeerConnection.js': 'info',
'modules/xmpp/strophe.util.js': 'log'
}
};
@@ -39,11 +40,11 @@ const DEFAULT_STATE = {
// Reduce default verbosity on mobile, it kills performance.
if (navigator.product === 'ReactNative') {
const RN_LOGGERS = {
'modules/sdp/SDPUtil.js': 'info' as LogLevel,
'modules/xmpp/ChatRoom.js': 'warn' as LogLevel,
'modules/xmpp/JingleSessionPC.js': 'info' as LogLevel,
'modules/xmpp/strophe.jingle.js': 'info' as LogLevel
const RN_LOGGERS: { [key: string]: LogLevel; } = {
'modules/sdp/SDPUtil.js': 'info',
'modules/xmpp/ChatRoom.js': 'warn',
'modules/xmpp/JingleSessionPC.js': 'info',
'modules/xmpp/strophe.jingle.js': 'info'
};
DEFAULT_STATE.config.loggers = {
@@ -52,16 +53,8 @@ if (navigator.product === 'ReactNative') {
};
}
type LogLevel = 'trace' | 'log' | 'info' | 'warn' | 'error';
export interface ILoggingState {
config: {
defaultLogLevel: LogLevel;
disableLogCollector?: boolean;
loggers: {
[key: string]: LogLevel;
};
};
config: ILoggingConfig;
logCollector?: {
flush: () => void;
start: () => void;

View File

@@ -0,0 +1,9 @@
export type LogLevel = 'trace' | 'log' | 'info' | 'warn' | 'error';
export interface ILoggingConfig {
defaultLogLevel?: LogLevel;
disableLogCollector?: boolean;
loggers?: {
[key: string]: LogLevel;
};
}

View File

@@ -228,7 +228,7 @@ class AudioTrack extends Component<IProps> {
* @returns {void}
*/
_detachTrack(track?: ITrack) {
if (this._ref?.current && track && track.jitsiTrack) {
if (this._ref?.current && track?.jitsiTrack) {
clearTimeout(this._playTimeout);
this._playTimeout = undefined;
track.jitsiTrack.detach(this._ref.current);

View File

@@ -358,7 +358,7 @@ class Video extends Component<IProps> {
* @returns {void}
*/
_detachTrack(videoTrack?: Partial<ITrack>) {
if (this._videoElement && videoTrack && videoTrack.jitsiTrack) {
if (this._videoElement && videoTrack?.jitsiTrack) {
videoTrack.jitsiTrack.detach(this._videoElement);
}
}

View File

@@ -19,11 +19,11 @@ export const MEDIA_TYPE: {
AUDIO: MediaType;
SCREENSHARE: MediaType;
VIDEO: MediaType;
} = {
AUDIO: 'audio',
SCREENSHARE: 'screenshare',
VIDEO: 'video'
};
} = {
AUDIO: 'audio',
SCREENSHARE: 'screenshare',
VIDEO: 'video'
};
/* eslint-disable no-bitwise */

View File

@@ -3,7 +3,6 @@ import { AnyAction, combineReducers } from 'redux';
import { CONFERENCE_FAILED, CONFERENCE_LEFT } from '../conference/actionTypes';
import ReducerRegistry from '../redux/ReducerRegistry';
import { TRACK_REMOVED } from '../tracks/actionTypes';
import { DefferedPromise, createDeferred } from '../util/helpers';
import {
GUM_PENDING,
@@ -93,17 +92,18 @@ function _audio(state: IAudioState = _AUDIO_INITIAL_MEDIA_STATE, action: AnyActi
// initial track creation haven't been started we would wait for it to finish before starting to join the room.
// NOTE: The previous implementation was using the GUM promise from conference.init. But it turned out that connect
// may finish even before conference.init is executed.
const DEFAULT_INITIAL_PROMISE_STATE = createDeferred<IInitialGUMPromiseResult>();
const DEFAULT_INITIAL_PROMISE_STATE = Promise.withResolvers<IInitialGUMPromiseResult>();
/**
* Reducer fot the common properties in media state.
* Reducer for the common properties in media state.
*
* @param {ICommonState} state - Common media state.
* @param {Object} action - Action object.
* @param {string} action.type - Type of action.
* @returns {ICommonState}
*/
function _initialGUMPromise(state: DefferedPromise<IInitialGUMPromiseResult> | null = DEFAULT_INITIAL_PROMISE_STATE,
function _initialGUMPromise(
state: PromiseWithResolvers<IInitialGUMPromiseResult> | null = DEFAULT_INITIAL_PROMISE_STATE,
action: AnyAction) {
if (action.type === SET_INITIAL_GUM_PROMISE) {
return action.promise ?? null;
@@ -294,7 +294,7 @@ interface IVideoState {
export interface IMediaState {
audio: IAudioState;
initialGUMPromise: DefferedPromise<IInitialGUMPromiseResult> | null;
initialGUMPromise: PromiseWithResolvers<IInitialGUMPromiseResult> | null;
screenshare: IScreenshareState;
video: IVideoState;
}

View File

@@ -240,7 +240,7 @@ export function participantJoined(participant: IParticipant) {
// conference. The following check is really necessary because a
// JitsiConference may have moved into leaving but may still manage to
// sneak a PARTICIPANT_JOINED in if its leave is delayed for any purpose
// (which is not outragous given that leaving involves network
// (which is not outrageous given that leaving involves network
// requests.)
const stateFeaturesBaseConference
= getState()['features/base/conference'];

View File

@@ -15,7 +15,6 @@ import i18next from '../i18n/i18next';
import { MEDIA_TYPE, MediaType, VIDEO_TYPE } from '../media/constants';
import { toState } from '../redux/functions';
import { getScreenShareTrack, isLocalTrackMuted } from '../tracks/functions.any';
import { createDeferred } from '../util/helpers';
import {
JIGASI_PARTICIPANT_ICON,
@@ -127,7 +126,7 @@ export function getActiveSpeakersToBeDisplayed(stateful: IStateful) {
* @returns {Promise}
*/
export function getFirstLoadableAvatarUrl(participant: IParticipant, store: IStore) {
const deferred: any = createDeferred();
const deferred: any = Promise.withResolvers();
const fullPromise = deferred.promise
.then(() => _getFirstLoadableAvatarUrl(participant, store))
.then((result: any) => {

View File

@@ -52,7 +52,7 @@ StateListenerRegistry.register(
/**
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
* tiles accodingly.
* tiles accordingly.
*
* @param {Array<string>} oldScreenshareSourceNames - List of old screenshare source names.
* @param {Array<string>} newScreenshareSourceNames - Current list of screenshare source names.

View File

@@ -340,9 +340,7 @@ class Popover extends Component<IProps, IState> {
_onTouchStart(event: TouchEvent) {
if (this.props.visible
&& !this.props.overflowDrawer
&& this._contextMenuRef
&& this._contextMenuRef.contains
&& !this._contextMenuRef.contains(event.target as Node)
&& !this._contextMenuRef?.contains?.(event.target as Node)
&& !this._containerRef?.current?.contains(event.target as Node)) {
this._onHideDialog();
}

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx';
import React, { ReactNode } from 'react';
import React, { ReactNode, useEffect, useRef, useState } from 'react';
import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
@@ -11,6 +11,7 @@ import { isButtonEnabled } from '../../../../toolbox/functions.web';
import { getConferenceName } from '../../../conference/functions';
import { PREMEETING_BUTTONS, THIRD_PARTY_PREJOIN_BUTTONS } from '../../../config/constants';
import { withPixelLineHeight } from '../../../styles/functions.web';
import Tooltip from '../../../tooltip/components/Tooltip';
import { isPreCallTestEnabled } from '../../functions';
import ConnectionStatus from './ConnectionStatus';
@@ -160,15 +161,20 @@ const useStyles = makeStyles()(theme => {
display: 'none'
}
},
roomNameContainer: {
width: '100%',
textAlign: 'center',
marginBottom: theme.spacing(4)
},
roomName: {
...withPixelLineHeight(theme.typography.heading5),
color: theme.palette.text01,
marginBottom: theme.spacing(4),
display: 'inline-block',
overflow: 'hidden',
textAlign: 'center',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
maxWidth: '100%',
}
};
});
@@ -195,6 +201,19 @@ const PreMeetingScreen = ({
backgroundSize: 'cover'
} : {};
const roomNameRef = useRef<HTMLSpanElement | null>(null);
const [ isOverflowing, setIsOverflowing ] = useState(false);
useEffect(() => {
if (roomNameRef.current) {
const element = roomNameRef.current;
const elementStyles = window.getComputedStyle(element);
const elementWidth = Math.floor(parseFloat(elementStyles.width));
setIsOverflowing(element.scrollWidth > elementWidth + 1);
}
}, [ _roomName ]);
return (
<div className = { clsx('premeeting-screen', classes.container, className) }>
<div style = { style }>
@@ -206,8 +225,22 @@ const PreMeetingScreen = ({
{title}
</h1>
{_roomName && (
<span className = { classes.roomName }>
{_roomName}
<span className = { classes.roomNameContainer }>
{isOverflowing ? (
<Tooltip content = { _roomName }>
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
</Tooltip>
) : (
<span
className = { classes.roomName }
ref = { roomNameRef }>
{_roomName}
</span>
)}
</span>
)}
{children}
@@ -259,3 +292,4 @@ function mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
}
export default connect(mapStateToProps)(PreMeetingScreen);

View File

@@ -227,14 +227,14 @@ class MeetingsList extends Component<IProps> {
<Container
className = { rootClassName }
key = { index }
onClick = { onPress }>
onClick = { onPress }
tabIndex = { 0 }>
<Container className = 'right-column'>
<Text
className = 'title'
onClick = { onPress }
onKeyPress = { onKeyPress }
role = 'button'
tabIndex = { 0 }>
role = 'button'>
{ title }
</Text>
{

View File

@@ -41,8 +41,8 @@ export function formatURLText(text = '') {
}
if (!result) {
// This will be the case for invalid URLs or URLs without a host (emails for example). In this case beacuse
// of the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@'
// This will be the case for invalid URLs or URLs without a host (emails for example). In this case due to
// the issue with PunycodeJS that truncates parts of the text when there is '@' we split the text by '@'
// and use punycode for every separate part to prevent homograph attacks.
result = text.split('@').map(punycode.toASCII)
.join('@');
@@ -59,7 +59,7 @@ export function formatURLText(text = '') {
*/
export function getSupportUrl(stateful: IStateful) {
// TODO: Once overwriting trough interface config is completelly gone we should think of a way to be able to set
// TODO: Once overwriting through interface config is completely gone we should think of a way to be able to set
// the value in the branding and not return the default value from interface config.
return toState(stateful)['features/dynamic-branding'].supportUrl || interfaceConfig?.SUPPORT_URL;
}

View File

@@ -12,7 +12,7 @@ declare let __DEV__: any;
/**
* Mixed type of the element (subtree) config. If it's a {@code boolean} (and is
* {@code true}), we persist the entire subtree. If it's an {@code Object}, we
* perist a filtered subtree based on the properties of the config object.
* persist a filtered subtree based on the properties of the config object.
*/
declare type ElementConfig = boolean | Object;

View File

@@ -96,8 +96,8 @@ class StateListenerRegistry {
* @returns {void}
*/
_listener({ prevSelections, store }: {
prevSelections: Map<SelectorListener, any>;
store: Store<any, any>;
prevSelections: Map<SelectorListener, any>;
store: Store<any, any>;
}) {
for (const selectorListener of this._selectorListeners) {
const prevSelection = prevSelections.get(selectorListener);

View File

@@ -5,7 +5,7 @@ import { setAspectRatio, setReducedUI } from './actions';
/**
* Middleware that handles widnow dimension changes and updates the aspect ratio and
* Middleware that handles window dimension changes and updates the aspect ratio and
* reduced UI modes accordingly.
*
* @param {Store} store - The redux store.

View File

@@ -375,7 +375,7 @@ export default class AbstractButton<P extends IProps, S = any> extends Component
// blur after click to release focus from button to allow PTT.
// @ts-ignore
e?.currentTarget?.blur && e.currentTarget.blur();
e?.currentTarget?.blur?.();
}
/**

View File

@@ -17,7 +17,7 @@ export * from './functions.any';
* and/or 'video'.
* @param {string|null} [options.micDeviceId] - Microphone device id or
* {@code undefined} to use app's settings.
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {number|undefined} [options.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {IStore} store - The redux store in the context of which the function
* is to execute and from which state such as {@code config} is to be retrieved.
* @returns {Promise<JitsiLocalTrack[]>}

View File

@@ -31,7 +31,7 @@ export * from './functions.any';
* and/or 'video'.
* @param {string|null} [options.micDeviceId] - Microphone device id or
* {@code undefined} to use app's settings.
* @param {number|undefined} [oprions.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {number|undefined} [options.timeout] - A timeout for JitsiMeetJS.createLocalTracks used to create the tracks.
* @param {IStore} store - The redux store in the context of which the function
* is to execute and from which state such as {@code config} is to be retrieved.
* @param {boolean} recordTimeMetrics - If true time metrics will be recorded.
@@ -60,7 +60,6 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
const {
desktopSharingFrameRate,
firefox_fake_device, // eslint-disable-line camelcase
resolution
} = state['features/base/config'];
const constraints = options.constraints ?? state['features/base/config'].constraints;
@@ -86,7 +85,6 @@ export function createLocalTracksF(options: ITrackOptions = {}, store?: IStore,
devices: options.devices?.slice(0),
effects,
facingMode: options.facingMode || getCameraFacingMode(state),
firefox_fake_device, // eslint-disable-line camelcase
micDeviceId,
resolution,
timeout

View File

@@ -12,7 +12,7 @@ export interface ITrackOptions {
};
};
desktopSharingSourceDevice?: string;
desktopSharingSources?: string[];
desktopSharingSources?: Array<DesktopSharingSourceType>;
devices?: string[];
facingMode?: string;
micDeviceId?: string | null;
@@ -67,9 +67,11 @@ export interface IToggleScreenSharingOptions {
shareOptions: IShareOptions;
}
export type DesktopSharingSourceType = 'screen' | 'window';
export interface IShareOptions {
desktopSharingSourceDevice?: string;
desktopSharingSources?: string[];
desktopSharingSources?: Array<DesktopSharingSourceType>;
desktopStream?: any;
}

View File

@@ -244,9 +244,9 @@ const ContextMenu = ({
list: Element | null,
currentFocus: Element | null,
traversalFunction: (
list: Element | null,
currentFocus: Element | null
) => Element | null
list: Element | null,
currentFocus: Element | null
) => Element | null
) => {
let wrappedOnce = false;
let nextFocus = traversalFunction(list, currentFocus);

View File

@@ -141,7 +141,7 @@ const useStyles = makeStyles()(theme => {
});
interface IObject {
[key: string]: string | string[] | boolean | number | number[] | {} | undefined;
[key: string]: unknown;
}
export interface IDialogTab<P> {

View File

@@ -98,6 +98,9 @@ const useStyles = makeStyles()(theme => {
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
},
'&.clearable-input': {
paddingRight: '46px'
}
},
@@ -121,10 +124,6 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer'
},
clearableInput: {
paddingRight: '46px'
},
clearButton: {
position: 'absolute',
right: '16px',
@@ -185,6 +184,7 @@ const Input = React.forwardRef<any, IProps>(({
}: IProps, ref) => {
const { classes: styles, cx } = useStyles();
const isMobile = isMobileBrowser();
const showClearIcon = clearable && value !== '' && !disabled;
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
onChange?.(e.target.value), []);
@@ -211,7 +211,7 @@ const Input = React.forwardRef<any, IProps>(({
autoComplete = { autoComplete }
autoFocus = { autoFocus }
className = { cx(styles.input, isMobile && 'is-mobile',
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
error && 'error', showClearIcon && 'clearable-input', icon && 'icon-input') }
disabled = { disabled }
id = { id }
maxLength = { maxLength }
@@ -232,7 +232,7 @@ const Input = React.forwardRef<any, IProps>(({
autoComplete = { autoComplete }
autoFocus = { autoFocus }
className = { cx(styles.input, isMobile && 'is-mobile',
error && 'error', clearable && styles.clearableInput, icon && 'icon-input') }
error && 'error', showClearIcon && 'clearable-input', icon && 'icon-input') }
data-testid = { testId }
disabled = { disabled }
id = { id }
@@ -252,7 +252,7 @@ const Input = React.forwardRef<any, IProps>(({
type = { type }
value = { value } />
)}
{clearable && !disabled && value !== '' && <button className = { styles.clearButton }>
{showClearIcon && <button className = { styles.clearButton }>
<Icon
onClick = { clearInput }
size = { 20 }

View File

@@ -19,12 +19,12 @@ interface ISelectProps {
className?: string;
/**
* Wether or not the select is disabled.
* Whether or not the select is disabled.
*/
disabled?: boolean;
/**
* Wether or not the select is in the error state.
* Whether or not the select is in the error state.
*/
error?: boolean;

View File

@@ -5,9 +5,9 @@ import { makeStyles } from 'tss-react/mui';
import { TEXT_OVERFLOW_TYPES } from '../../constants.web';
interface ITextWithOverflowProps {
children: ReactNode;
className?: string;
overflowType?: TEXT_OVERFLOW_TYPES;
children: ReactNode;
className?: string;
overflowType?: TEXT_OVERFLOW_TYPES;
}
const useStyles = makeStyles<{ translateDiff: number; }>()((_, { translateDiff }) => {

View File

@@ -13,7 +13,7 @@ import { SECURITY_URL } from './contants';
*
* @param {IReduxState} state - The redux state.
* @param {Function} t - The translation function.
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warning.
* @returns {Text}
*/
export default function getUnsafeRoomText(state: IReduxState, t: Function, context: 'meeting' | 'prejoin' | 'welcome') {

View File

@@ -6,7 +6,7 @@ import { SECURITY_URL } from './contants';
* Gets the unsafe room text for the given context.
*
* @param {Function} t - The translation function.
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warining.
* @param {'meeting'|'prejoin'|'welcome'} context - The given context of the warning.
* @returns {string}
*/
export default function getUnsafeRoomText(t: Function, context: 'meeting' | 'prejoin' | 'welcome') {

View File

@@ -22,27 +22,6 @@ export function assignIfDefined(target: Object, source: Object) {
return to;
}
export type DefferedPromise<T> = {
promise: Promise<T>;
reject: (reason?: any) => void;
resolve: (value: T) => void;
};
/**
* Creates a deferred object.
*
* @returns {{promise, resolve, reject}}
*/
export function createDeferred<T>() {
const deferred = {} as DefferedPromise<T>;
deferred.promise = new Promise<T>((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
const MATCH_OPERATOR_REGEXP = /[|\\{}()[\]^$+*?.-]/g;

View File

@@ -18,7 +18,7 @@ import CalendarListContent from './CalendarListContent.native';
import styles from './styles';
/**
* The tyoe of the React {@code Component} props of {@link CalendarList}.
* The type of the React {@code Component} props of {@link CalendarList}.
*/
interface IProps extends WithTranslation {

View File

@@ -152,7 +152,7 @@ export default createStyleSheet({
},
/**
* The container for all the lines if the norification.
* The container for all the lines if the notification.
*/
notificationTextContainer: {
flexDirection: 'column',

View File

@@ -5,8 +5,6 @@ import { v4 as uuidV4 } from 'uuid';
import { findWindows } from 'windows-iana';
import { IanaName } from 'windows-iana/dist/enums';
// @ts-expect-error
import { createDeferred } from '../../../../modules/util/helpers';
import { IStore } from '../../app/types';
import { parseURLParams } from '../../base/util/parseURLParams';
import { parseStandardURIString } from '../../base/util/uri';
@@ -153,8 +151,7 @@ export const microsoftCalendarApi = {
return Promise.reject('Sign in already in progress.');
}
const signInDeferred = createDeferred();
const signInDeferred = Promise.withResolvers();
const guids = {
authState: uuidV4(),
authNonce: uuidV4()
@@ -229,7 +226,7 @@ export const microsoftCalendarApi = {
userSigninName: tokenParts.userSigninName
}));
signInDeferred.resolve();
signInDeferred.resolve(undefined);
}
window.addEventListener('message', handleAuth);

View File

@@ -1,5 +1,5 @@
import { Theme } from '@mui/material';
import React, { useCallback, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { connect } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
@@ -264,7 +264,7 @@ const ChatMessage = ({
*
* @returns {React$Element<*>}
*/
const renderReactions = () => {
const renderReactions = useMemo(() => {
if (!message.reactions || message.reactions.size === 0) {
return null;
}
@@ -321,7 +321,7 @@ const ChatMessage = ({
</div>
</Popover>
);
};
}, [ message?.reactions, isHovered, isReactionsOpen ]);
return (
<div
@@ -366,7 +366,7 @@ const ChatMessage = ({
<div className = { classes.chatMessageFooterLeft }>
{message.reactions && message.reactions.size > 0 && (
<>
{renderReactions()}
{renderReactions}
</>
)}
</div>

View File

@@ -117,7 +117,7 @@ class DisplayNameForm extends Component<IProps, IState> {
* @returns {void}
*/
_onSubmit(event: any) {
event?.preventDefault && event.preventDefault();
event?.preventDefault?.();
// Store display name in settings
this.props.dispatch(updateSettings({

View File

@@ -349,8 +349,8 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
*/
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; }
displayName?: string; isGuest?: boolean; message: string; messageId?: string;
participantId: string; privateMessage: boolean; timestamp: number; }
) {
const isGif = isGifEnabled(store.getState()) && isGifMessage(message);
@@ -479,8 +479,8 @@ function getLobbyChatDisplayName(state: IReduxState, participantId: string) {
*/
function _handleReceivedMessage({ dispatch, getState }: IStore,
{ displayName, isGuest, lobbyChat, message, messageId, participantId, privateMessage, timestamp }: {
displayName?: string; isGuest?: boolean; lobbyChat: boolean; message: string;
messageId?: string; participantId: string; privateMessage: boolean; timestamp: number; },
displayName?: string; isGuest?: boolean; lobbyChat: boolean; message: string;
messageId?: string; participantId: string; privateMessage: boolean; timestamp: number; },
shouldPlaySound = true,
isReaction = false
) {

View File

@@ -11,7 +11,7 @@ import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
*
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
* instance which initiated the kick event.
* @param {?Function} submit - The function to execute after submiting the dialog.
* @param {?Function} submit - The function to execute after submitting the dialog.
* @returns {Function}
*/
export function notifyKickedOut(participant: any, submit?: Function) {
@@ -38,7 +38,7 @@ export function notifyKickedOut(participant: any, submit?: Function) {
* Notify that we've been kicked out of the conference.
*
* @param {string} reasonKey - The translation key for the reason why the conference failed.
* @param {?Function} submit - The function to execute after submiting the dialog.
* @param {?Function} submit - The function to execute after submitting the dialog.
* @returns {Function}
*/
export function notifyConferenceFailed(reasonKey: string, submit?: Function) {

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
@@ -7,6 +8,7 @@ import { IconUsers } from '../../../base/icons/svg';
import Label from '../../../base/label/components/web/Label';
import { COLORS } from '../../../base/label/constants';
import { getParticipantCount } from '../../../base/participants/functions';
import Tooltip from '../../../base/tooltip/components/Tooltip';
import SpeakerStats from '../../../speaker-stats/components/web/SpeakerStats';
import { isSpeakerStatsDisabled } from '../../../speaker-stats/functions';
import { iAmVisitor } from '../../../visitors/functions';
@@ -23,6 +25,7 @@ function SpeakerStatsLabel() {
const iAmVisitorState = useSelector(iAmVisitor);
const _isSpeakerStatsDisabled = useSelector(isSpeakerStatsDisabled);
const dispatch = useDispatch();
const { t } = useTranslation();
// visitor has hidden its own video and should not count itself
if (iAmVisitorState) {
@@ -38,13 +41,17 @@ function SpeakerStatsLabel() {
}
return (
<Label
color = { COLORS.white }
icon = { IconUsers }
iconColor = '#fff'
// eslint-disable-next-line react/jsx-no-bind
onClick = { onClick }
text = { `${count}` } />
<Tooltip
content = { t('speakerStats.labelTooltip', { count }) }
position = { 'bottom' }>
<Label
color = { COLORS.white }
icon = { IconUsers }
iconColor = '#fff'
// eslint-disable-next-line react/jsx-no-bind
onClick = { onClick }
text = { `${count}` } />
</Tooltip>
);
}

View File

@@ -8,5 +8,5 @@ export const IFRAME_DISABLED_TIMEOUT_MINUTES = 5;
/**
* A list of allowed location to embed iframe.
*/
/* eslint-disable-next-line no-extra-parens*/
/* eslint-disable-next-line no-extra-parens */
export const IFRAME_EMBED_ALLOWED_LOCATIONS = ([] as string[]).concat(ADDITIONAL_LOCATIONS);

View File

@@ -1,7 +1,12 @@
import { openDialog } from '../base/dialog/actions';
import { DesktopSharingSourceType } from '../base/tracks/types';
import DesktopPicker from './components/DesktopPicker';
type Options = {
desktopSharingSources?: Array<DesktopSharingSourceType>;
};
/**
* Signals to open a dialog with the DesktopPicker component.
*
@@ -10,7 +15,7 @@ import DesktopPicker from './components/DesktopPicker';
* a DesktopCapturerSource has been chosen.
* @returns {Object}
*/
export function showDesktopPicker(options: { desktopSharingSources?: any; } = {}, onSourceChoose: Function) {
export function showDesktopPicker(options: Options = {}, onSourceChoose: Function) {
const { desktopSharingSources } = options;
return openDialog(DesktopPicker, {

View File

@@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { IStore } from '../../app/types';
import { hideDialog } from '../../base/dialog/actions';
import { translate } from '../../base/i18n/functions';
import { DesktopSharingSourceType } from '../../base/tracks/types';
import Dialog from '../../base/ui/components/web/Dialog';
import Tabs from '../../base/ui/components/web/Tabs';
import { THUMBNAIL_SIZE } from '../constants';
@@ -32,7 +33,7 @@ const TAB_LABELS = {
window: 'dialog.applicationWindow'
};
const VALID_TYPES = Object.keys(TAB_LABELS);
const VALID_TYPES = Object.keys(TAB_LABELS) as DesktopSharingSourceType[];
/**
* The type of the React {@code Component} props of {@link DesktopPicker}.
@@ -42,7 +43,7 @@ interface IProps extends WithTranslation {
/**
* An array with desktop sharing sources to be displayed.
*/
desktopSharingSources: Array<string>;
desktopSharingSources: Array<DesktopSharingSourceType>;
/**
* Used to request DesktopCapturerSources.
@@ -84,7 +85,7 @@ interface IState {
/**
* The desktop source types to fetch previews for.
*/
types: Array<string>;
types: Array<DesktopSharingSourceType>;
}
@@ -112,7 +113,7 @@ class DesktopPicker extends PureComponent<IProps, IState> {
* @private
* @returns {Array<string>} The filtered types.
*/
static _getValidTypes(types: string[] = []) {
static _getValidTypes(types: DesktopSharingSourceType[] = []) {
return types.filter(
type => VALID_TYPES.includes(type));
}
@@ -346,10 +347,10 @@ class DesktopPicker extends PureComponent<IProps, IState> {
= types.map(
type => {
return {
accessibilityLabel: t(TAB_LABELS[type as keyof typeof TAB_LABELS]),
accessibilityLabel: t(TAB_LABELS[type]),
id: `${type}`,
controlsId: `${type}-panel`,
label: t(TAB_LABELS[type as keyof typeof TAB_LABELS])
label: t(TAB_LABELS[type])
};
});

View File

@@ -1,6 +1,4 @@
import logger from './logger';
import { ElectronWindowType } from './types';
/**
* Begins a request to get available DesktopCapturerSources.
@@ -13,23 +11,6 @@ import { ElectronWindowType } from './types';
* @returns {Function}
*/
export function obtainDesktopSources(options: { thumbnailSize?: Object; types: string[]; }) {
const { JitsiMeetElectron } = window as ElectronWindowType;
// TODO: delete this after 2 releases
if (JitsiMeetElectron?.obtainDesktopStreams) {
return new Promise((resolve, reject) => {
JitsiMeetElectron.obtainDesktopStreams(
(sources: Array<{ id: string; }>) => resolve(_separateSourcesByType(sources)),
(error: Error) => {
logger.error(
`Error while obtaining desktop sources: ${error}`);
reject(error);
},
options
);
});
}
return APP.API.requestDesktopSources(options).then(
({ sources, error }: { error: Error; sources: Array<{ id: string; }>; }) => {
if (sources) {
@@ -43,21 +24,6 @@ export function obtainDesktopSources(options: { thumbnailSize?: Object; types: s
});
}
/**
* Check usage of old jitsi meet electron version.
*
* @returns {boolean} True if we use old jitsi meet electron, otherwise false.
*/
export function oldJitsiMeetElectronUsage() {
const { JitsiMeetElectron } = window as ElectronWindowType;
if (JitsiMeetElectron?.obtainDesktopStreams) {
return true;
}
return false;
}
/**
* Converts an array of DesktopCapturerSources to an object with types for keys
* and values being an array with sources of the key's type.

View File

@@ -1,5 +0,0 @@
export type ElectronWindowType = {
JitsiMeetElectron?: {
obtainDesktopStreams: Function;
} ;
} & typeof window;

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