Compare commits

...

29 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
349d66dd6b feat(rn,invite) add ability to disable dial-in options 2024-01-22 13:05:07 +01:00
Calin-Teodor
9cd9a85a4d feat(settings/native): hide login/logout for 8x8.vc on profile screen 2024-01-11 11:31:56 +01:00
Calinteodor
5ce3f02c03 feat(invite/security): Brave issues fixes (#14180)
* feat(invite/security): fix share icon/lobby mode switch
2024-01-11 11:30:42 +01:00
Saúl Ibarra Corretgé
5bd7b82502 fix(rn,room-lock) use numeric input for password dialog if appropriate (#14142) 2024-01-11 11:28:09 +01:00
Calin-Teodor
5d77491c8c chore(ios, pods): sync podfile 2023-11-22 18:14:46 +02:00
Calin-Teodor
934a7128b0 chore(rn, version): bump app and sdk version 2023-11-22 17:20:49 +02:00
Calinteodor
1d8a3a761f fix(sdk): custom server url is overwritten by sdk default url option value (#14092)
* fix(sdk): custom server url is overwritten by sdk default url option value
2023-11-22 17:18:18 +02:00
Calin-Teodor
c305d5a6e9 chore(rnsdk, version): bump to 1.0.3 2023-11-22 16:47:12 +02:00
Calin-Teodor
3d0b3a2f71 feat(react-native-sdk): null error fix for ios 2023-11-22 16:46:27 +02:00
Calin-Teodor
7d48819619 chore(rnsdk, version): bump to 1.0.2 2023-11-21 19:27:08 +02:00
Calinteodor
c9807cbc1b sdk(react-native-sdk): update script for rnsdk dependencies (#14069)
* sdk(react-native-sdk): update script for rnsdk dependencies
Some of our peer dependencies use github urls that need to be taken in consideration.
2023-11-21 19:25:49 +02:00
Calin-Teodor
155abe258c chore(jitsi-rnsdk, version): bump to 1.0.1 2023-11-21 19:02:48 +02:00
Calin-Teodor
e50477ca8d sdk(react-native-sdk/android): replaced activityContext with currentActivity 2023-11-21 18:57:09 +02:00
Calin-Teodor
fb45fc4db4 chore(rn-orientation-locker, version): bump to latest 2023-11-21 18:56:30 +02:00
Calin-Teodor
32e018f43b chore(rnsdk, version): bump to 1.0.0 2023-11-17 13:00:58 +02:00
Abbas Al-Mansoori
e3dd39f6e4 refactor(rnsdk): remove redundant audio and video actions (#14066)
* refactor(rnsdk): remove redundant audio and video actions
2023-11-17 12:27:56 +02:00
Philipp Fruck
92c43c03ad fix(native app): Display poll creator name
In the jitsi web app, the poll creator is displayed
for all published votes whereas in the native app
the current username of the participant has been
displayed for all polls regardless of the creator
2023-11-16 12:57:28 +02:00
Abbas Al-Mansoori
8d8bd4d8d4 feat(rnsdk): add audio and video muted state changed 2023-11-16 12:57:00 +02:00
Calin-Teodor
8dda34a502 feat(authentication): group config options inside an object param 2023-11-15 16:33:39 +02:00
Calin-Teodor
3a85a915bf feat(authentication): used config for control over joining audio/video mute/unmute 2023-11-15 12:43:54 +02:00
Calinteodor
17b0a12224 ref(authentication): handle joining in low bandwidth mode (#14032)
ref(authentication): handle joining in low bandwidth mode
2023-11-14 12:16:53 +02:00
Calin-Teodor
b52394d328 feat(base/ui): removed unneeded lineHeight value 2023-11-09 12:12:10 +02:00
Calinteodor
cadb216cea feat(base/modal): changed hasTabNavigator to hasExtraHeaderHeight (#14033)
* feat(base/modal): changed hasTabNavigator to hasExtraHeaderHeight
2023-11-08 12:01:09 +02:00
Calinteodor
2bf8e6cfce feat(filmstrip): fixed indicators container dissapear when not in tile view (#14031)
* feat(filmstrip): fixed indicators container dissapear when not in tile view
2023-11-08 10:42:00 +02:00
Calin-Teodor
261a8a6740 feat(participants-pane): fixed visitors label position 2023-11-07 18:40:09 +02:00
Calin-Teodor
35dbcd9b9d feat(filmstrip): fixed indicators container ui 2023-11-07 18:39:46 +02:00
Calinteodor
45bf00d096 sdk(react-native-sdk): rnsdk screenshare android fix (#13884)
sdk(react-native-sdk): rnsdk screenshare android fix
2023-11-07 12:23:17 +02:00
Calinteodor
78847148d8 feat(recent-list): fix undefined error that breaks visitor joining (#14024)
* feat(recent-list): fix undefined error that breaks visitor joining

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

* feat(recent-list): fixed linter
2023-11-06 23:13:10 +02:00
Calin-Teodor
c53e7ff9cd chore(rn, versions): bump app and sdk versions 2023-10-30 15:46:50 +01:00
54 changed files with 859 additions and 263 deletions

4
.gitignore vendored
View File

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

View File

@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Server URL configuration -->
<restriction
android:defaultValue="https://meet.jit.si"
android:description="@string/restriction_server_url_description"
android:key="SERVER_URL"
android:restrictionType="string"
android:title="@string/restriction_server_url_title"/>
</restrictions>
<?xml version="1.0" encoding="utf-8"?>
<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Server URL configuration -->
<restriction
android:description="@string/restriction_server_url_description"
android:key="SERVER_URL"
android:restrictionType="string"
android:title="@string/restriction_server_url_title"/>
</restrictions>

View File

@@ -26,5 +26,5 @@ android.useAndroidX=true
android.enableJetifier=true
android.bundle.enableUncompressedNativeLibs=false
appVersion=99.0.0
sdkVersion=99.0.0
appVersion=23.6.1
sdkVersion=8.6.1

View File

@@ -373,7 +373,7 @@ PODS:
- React
- react-native-netinfo (9.4.1):
- React-Core
- react-native-orientation-locker (1.5.0):
- react-native-orientation-locker (1.6.0):
- React-Core
- react-native-pager-view (6.2.0):
- React-Core
@@ -752,7 +752,7 @@ SPEC CHECKSUMS:
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
react-native-orientation-locker: 4409c5b12b65f942e75449872b4f078b6f27af81
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2

View File

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

View File

@@ -38,7 +38,6 @@
[builder setFeatureFlag:@"resolution" withValue:@(360)];
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
[builder setFeatureFlag:@"ios.recording.enabled" withBoolean:YES];
builder.serverURL = [NSURL URLWithString:@"https://meet.jit.si"];
}];
[jitsiMeet application:application didFinishLaunchingWithOptions:launchOptions];
@@ -126,7 +125,7 @@
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(UIWindow *)window {
return [[JitsiMeet sharedInstance] application:application
return [[JitsiMeet sharedInstance] application:application
supportedInterfaceOrientationsForWindow:window];
}

View File

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

View File

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

View File

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

View File

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

View File

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

14
package-lock.json generated
View File

@@ -84,7 +84,7 @@
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"react-native-orientation-locker": "1.6.0",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
@@ -15919,10 +15919,9 @@
"integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
},
"node_modules/react-native-orientation-locker": {
"version": "1.5.0",
"resolved": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"integrity": "sha512-fR/lyo2Pqlf7ubWAhUmOKku7Ciaf83q6f7sNgYvPGUhDEjWUy/lyOJu2WZBnM4XdAmftBVSXl/JhBQAqXcckVw==",
"license": "MIT",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/react-native-orientation-locker/-/react-native-orientation-locker-1.6.0.tgz",
"integrity": "sha512-D3IOtAcaAi6S2w0Y1EUnr16I47isosQbE7F67fAu9A+gE67NkyKaJ9HL5EsZ+Uc7+7m+NsuBjx3dxuANNy8rVA==",
"peerDependencies": {
"react": ">=16.13.1",
"react-native": ">=0.63.2",
@@ -32191,8 +32190,9 @@
"integrity": "sha512-0Fotox+eLXQooeibVs3P60yASYUWjtRw9MZNmbuHt5UZQrgUrAKsE4jm7gTr4tPU1m1RkwGzcgUFpcOkh/ec7g=="
},
"react-native-orientation-locker": {
"version": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"integrity": "sha512-fR/lyo2Pqlf7ubWAhUmOKku7Ciaf83q6f7sNgYvPGUhDEjWUy/lyOJu2WZBnM4XdAmftBVSXl/JhBQAqXcckVw=="
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/react-native-orientation-locker/-/react-native-orientation-locker-1.6.0.tgz",
"integrity": "sha512-D3IOtAcaAi6S2w0Y1EUnr16I47isosQbE7F67fAu9A+gE67NkyKaJ9HL5EsZ+Uc7+7m+NsuBjx3dxuANNy8rVA=="
},
"react-native-pager-view": {
"version": "6.2.0",

View File

@@ -90,7 +90,7 @@
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "https://github.com/jitsi/react-native-orientation-locker/releases/download/v1.5.0-jitsi1/react-native-orientation-locker-1.5.0.tgz",
"react-native-orientation-locker": "1.6.0",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,98 @@
/*
* Copyright @ 2019-present 8x8, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
import java.util.Random;
/**
* Helper class for creating the ongoing notification which is used with
* {@link JitsiMeetOngoingConferenceService}. It allows the user to easily get back to the app
* and to hangup from within the notification itself.
*/
class RNOngoingNotification {
private static final String TAG = RNOngoingNotification.class.getSimpleName();
static final int NOTIFICATION_ID = new Random().nextInt(99999) + 10000;
static void createOngoingConferenceNotificationChannel(Activity currentActivity) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return;
}
if (currentActivity == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification channel: no current context");
return;
}
NotificationManager notificationManager
= (NotificationManager) currentActivity.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel
= notificationManager.getNotificationChannel("JitsiOngoingConferenceChannel");
if (channel != null) {
// The channel was already created, no need to do it again.
return;
}
channel = new NotificationChannel("JitsiOngoingConferenceChannel", currentActivity.getString(R.string.ongoing_notification_channel_name), NotificationManager.IMPORTANCE_DEFAULT);
channel.enableLights(false);
channel.enableVibration(false);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
}
static Notification buildOngoingConferenceNotification(Context context) {
if (context == null) {
JitsiMeetLogger.w(TAG + " Cannot create notification: no current context");
return null;
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "JitsiOngoingConferenceChannel");
builder
.setCategory(NotificationCompat.CATEGORY_CALL)
.setContentTitle(context.getString(R.string.ongoing_notification_title))
.setContentText(context.getString(R.string.ongoing_notification_text))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setUsesChronometer(true)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setOnlyAlertOnce(true)
.setSmallIcon(context.getResources().getIdentifier("ic_notification", "drawable", context.getPackageName()));
return builder.build();
}
}

View File

@@ -19,6 +19,8 @@ import { setAudioMuted, setVideoMuted } from './react/features/base/media/action
interface IEventListeners {
onAudioMutedChanged?: Function;
onVideoMutedChanged?: Function;
onConferenceBlurred?: Function;
onConferenceFocused?: Function;
onConferenceJoined?: Function;
@@ -107,6 +109,8 @@ export const JitsiMeeting = forwardRef((props: IAppProps, ref) => {
setAppProps({
'flags': flags,
'rnSdkHandlers': {
onAudioMutedChanged: eventListeners?.onAudioMutedChanged,
onVideoMutedChanged: eventListeners?.onVideoMutedChanged,
onConferenceBlurred: eventListeners?.onConferenceBlurred,
onConferenceFocused: eventListeners?.onConferenceFocused,
onConferenceJoined: eventListeners?.onConferenceJoined,

View File

@@ -1,16 +1,16 @@
{
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"version": "1.0.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"version": "1.0.3",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
@@ -19,7 +19,7 @@
"@react-navigation/native": "6.1.7",
"@react-navigation/stack": "6.3.17",
"@xmldom/xmldom": "0.8.7",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
@@ -27,7 +27,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/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -69,7 +69,7 @@
"react-native-get-random-values": "1.9.0",
"react-native-immersive-mode": "2.0.1",
"react-native-keep-awake": "4.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-orientation-locker": "1.6.0",
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
@@ -80,7 +80,7 @@
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.3",
"react-native-webrtc": "111.0.6",
"react-native-webview": "13.5.1"
}
},
@@ -396,13 +396,13 @@
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"node_modules/@jitsi/js-utils": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"dependencies": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
}
},
"node_modules/@jitsi/js-utils/node_modules/js-md5": {
@@ -993,9 +993,23 @@
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/binary-extensions": {
"version": "2.2.0",
@@ -1010,11 +1024,6 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"node_modules/bowser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz",
"integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2481,14 +2490,14 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"integrity": "sha512-ky3q5gLeLDHFtenXe7+QlxAh33opz0a3bqEp8svA9urR1AICUi0QthOwmUerTdfSRFU3wfVObF9axlX1fbHZ3A==",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"integrity": "sha512-+6nI2ExiTS8aKnq3ZDpYKs1MSDtUHaK4hIhVxyI7Db6ZojrMOpZx6lnrmqz+AHWllSHvIggFz48du7tG6UYANw==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/rtcstats": "9.6.0",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -2509,6 +2518,30 @@
"webrtc-adapter": "8.1.1"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/rtcstats": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.6.0.tgz",
"integrity": "sha512-eFFD6vp1dFwvjAR+NRjsxlvclFvqo/nbU2RcSLzXbIjG/WraMHyxMSzllZyT1T3S2iPehS4dnZ/ZYyz5WYHTaA==",
"dependencies": {
"@jitsi/js-utils": "^2.2.0",
"@jitsi/logger": "2.0.2",
"sdp": "^3.0.3",
"uuid": "^8.3.2"
}
},
"node_modules/lib-jitsi-meet/node_modules/@jitsi/rtcstats/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/lib-jitsi-meet/node_modules/base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"node_modules/lib-jitsi-meet/node_modules/uuid": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
@@ -3570,6 +3603,24 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
}
],
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@@ -4044,13 +4095,13 @@
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
},
"@jitsi/js-utils": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.2.1.tgz",
"integrity": "sha512-4Ia4hWO7aTMGbYftzeBr+IHIu5YxiWwTlhsSK34z6925oNAUNI863WgYYGTcXkW/1yuM6LBZrnuZBySDqosISA==",
"requires": {
"@hapi/bourne": "^3.0.0",
"bowser": "2.7.0",
"js-md5": "0.7.3"
"js-md5": "0.7.3",
"ua-parser-js": "1.0.35"
},
"dependencies": {
"js-md5": {
@@ -4445,9 +4496,9 @@
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"binary-extensions": {
"version": "2.2.0",
@@ -4459,11 +4510,6 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
},
"bowser": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-2.7.0.tgz",
"integrity": "sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w=="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -5476,12 +5522,12 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"integrity": "sha512-ky3q5gLeLDHFtenXe7+QlxAh33opz0a3bqEp8svA9urR1AICUi0QthOwmUerTdfSRFU3wfVObF9axlX1fbHZ3A==",
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"integrity": "sha512-+6nI2ExiTS8aKnq3ZDpYKs1MSDtUHaK4hIhVxyI7Db6ZojrMOpZx6lnrmqz+AHWllSHvIggFz48du7tG6UYANw==",
"requires": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@jitsi/rtcstats": "9.6.0",
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
"@jitsi/sdp-simulcast": "0.4.0",
"@testrtc/watchrtc-sdk": "1.36.3",
@@ -5502,6 +5548,29 @@
"webrtc-adapter": "8.1.1"
},
"dependencies": {
"@jitsi/rtcstats": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/@jitsi/rtcstats/-/rtcstats-9.6.0.tgz",
"integrity": "sha512-eFFD6vp1dFwvjAR+NRjsxlvclFvqo/nbU2RcSLzXbIjG/WraMHyxMSzllZyT1T3S2iPehS4dnZ/ZYyz5WYHTaA==",
"requires": {
"@jitsi/js-utils": "^2.2.0",
"@jitsi/logger": "2.0.2",
"sdp": "^3.0.3",
"uuid": "^8.3.2"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"uuid": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.1.0.tgz",
@@ -6263,6 +6332,11 @@
"is-typed-array": "^1.1.9"
}
},
"ua-parser-js": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.35.tgz",
"integrity": "sha512-fKnGuqmTBnIE+/KXSzCn4db8RTigUzw1AN0DmdU6hJovUTbYJKyqj+8Mt1c4VfRDnOVJnENmfYkIPZ946UrSAA=="
},
"uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@jitsi/react-native-sdk",
"version": "0.0.0",
"version": "1.0.3",
"description": "React Native SDK for Jitsi Meet.",
"main": "index.tsx",
"license": "Apache-2.0",
@@ -11,7 +11,7 @@
"url": "git+https://github.com/jitsi/jitsi-meet.git"
},
"dependencies": {
"@jitsi/js-utils": "2.1.3",
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",
"@jitsi/rtcstats": "9.5.1",
"@react-navigation/bottom-tabs": "6.5.8",
@@ -20,7 +20,7 @@
"@react-navigation/native": "6.1.7",
"@react-navigation/stack": "6.3.17",
"@xmldom/xmldom": "0.8.7",
"base64-js": "1.3.1",
"base64-js": "1.5.1",
"grapheme-splitter": "1.0.4",
"i18n-iso-countries": "6.8.0",
"i18next": "17.0.6",
@@ -28,7 +28,7 @@
"i18next-http-backend": "^2.2.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1687.0.0+cafe30d7/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1716.0.0+93c167d3/lib-jitsi-meet.tgz",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -73,7 +73,7 @@
"react-native-pager-view": "6.2.0",
"react-native-paper": "5.10.3",
"react-native-performance": "5.0.0",
"react-native-orientation-locker": "1.5.0",
"react-native-orientation-locker": "1.6.0",
"react-native-safe-area-context": "4.7.1",
"react-native-screens": "3.24.0",
"react-native-sound": "0.11.2",
@@ -81,7 +81,7 @@
"react-native-svg": "13.13.0",
"react-native-video": "6.0.0-alpha.7",
"react-native-watch-connectivity": "1.1.0",
"react-native-webrtc": "111.0.3",
"react-native-webrtc": "111.0.6",
"react-native-webview": "13.5.1"
},
"overrides": {
@@ -97,4 +97,4 @@
"keywords": [
"react-native"
]
}
}

View File

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

View File

@@ -25,7 +25,8 @@ function updateDependencies() {
updated = true;
}
if (!semver.valid(packageJSON.dependencies[key])) {
if (!semver.valid(packageJSON.dependencies[key])
&& packageJSON.dependencies[key] !== RNSDKpackageJSON.peerDependencies[key]) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
@@ -46,6 +47,18 @@ function updateDependencies() {
console.log(`${key} is now set to ${RNSDKpackageJSON.peerDependencies[key]}`);
}
if (!semver.valid(RNSDKpackageJSON.peerDependencies[key])
&& RNSDKpackageJSON.peerDependencies[key].includes('github')
&& packageJSON.dependencies[key] !== RNSDKpackageJSON.peerDependencies[key]) {
packageJSON.dependencies[key] = RNSDKpackageJSON.peerDependencies[key];
updated = true;
console.log(
`A fix for ${key} is available on ${RNSDKpackageJSON.peerDependencies[key]}.
This is now set on your end.`
);
}
}
packageJSON.overrides = packageJSON.overrides || {};

View File

@@ -5,12 +5,15 @@ import { openTokenAuthUrl } from '../authentication/actions';
// @ts-ignore
import { getTokenAuthUrl, isTokenAuthEnabled } from '../authentication/functions';
import { getJwtExpirationDate } from '../base/jwt/functions';
import { MEDIA_TYPE } from '../base/media/constants';
import { isLocalTrackMuted } from '../base/tracks/functions.any';
import { getLocationContextRoot, parseURIString } from '../base/util/uri';
import { addTrackStateToURL } from './functions.any';
import logger from './logger';
import { IStore } from './types';
/**
* Redirects to another page generated by replacing the path in the original URL
* with the given path.
@@ -104,7 +107,11 @@ export function maybeRedirectToTokenAuthUrl(
dispatch: IStore['dispatch'], getState: IStore['getState'], failureCallback: Function) {
const state = getState();
const config = state['features/base/config'];
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const { startAudioOnly } = config;
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
if (!isTokenAuthEnabled(config)) {
return false;
@@ -120,7 +127,18 @@ export function maybeRedirectToTokenAuthUrl(
const room = state['features/base/conference'].room;
const { tenant } = parseURIString(locationURL.href) || {};
getTokenAuthUrl(config, room, tenant, true, locationURL)
getTokenAuthUrl(
config,
locationURL,
{
audioMuted,
audioOnlyEnabled: audioOnlyEnabled || startAudioOnly,
skipPrejoin: true,
videoMuted
},
room,
tenant
)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');

View File

@@ -21,6 +21,7 @@ import { AbstractApp, IProps as AbstractAppProps } from './AbstractApp';
import '../middlewares.native';
import '../reducers.native';
declare let __DEV__: any;
const { AppInfo } = NativeModules;
@@ -110,7 +111,7 @@ export class App extends AbstractApp<IProps> {
*/
async _extraInit() {
const { dispatch, getState } = this.state.store ?? {};
const { flags = {} } = this.props;
const { flags = {}, url, userInfo } = this.props;
let callIntegrationEnabled = flags[CALL_INTEGRATION_ENABLED as keyof typeof flags];
// CallKit does not work on the simulator, make sure we disable it.
@@ -153,16 +154,18 @@ export class App extends AbstractApp<IProps> {
await rootNavigationReady;
// Update specified server URL.
if (typeof this.props.url !== 'undefined') {
if (typeof url !== 'undefined') {
// @ts-ignore
const { serverURL } = this.props.url;
const { serverURL } = url;
if (typeof serverURL !== 'undefined') {
dispatch?.(updateSettings({ serverURL }));
}
}
dispatch?.(updateSettings(this.props.userInfo || {}));
// @ts-ignore
dispatch?.(updateSettings(userInfo || {}));
// Update settings with feature-flag.
if (typeof callIntegrationEnabled !== 'undefined') {

View File

@@ -55,8 +55,20 @@ function _getWebConferenceRoute(state: IReduxState) {
&& !state['features/base/jwt'].jwt && room) {
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const { startAudioOnly } = config;
return getTokenAuthUrl(config, room, tenant, false, locationURL)
return getTokenAuthUrl(
config,
locationURL,
{
audioMuted: false,
audioOnlyEnabled: startAudioOnly,
skipPrejoin: false,
videoMuted: false
},
room,
tenant
)
.then((url: string | undefined) => {
route.href = url;

View File

@@ -13,29 +13,65 @@ export const isTokenAuthEnabled = (config: IConfig): boolean =>
/**
* Returns the state that we can add as a parameter to the tokenAuthUrl.
*
* @param {URL} locationURL - The location URL.
* @param {Object} options: - Config options {
* audioMuted: boolean | undefined
* audioOnlyEnabled: boolean | undefined,
* skipPrejoin: boolean | undefined,
* videoMuted: boolean | undefined
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
* @param {boolean} skipPrejoin - Whether to skip pre-join page.
* @param {URL} locationURL - The location URL.
*
* @returns {Object} The state object.
*/
export const _getTokenAuthState = (
locationURL: URL,
options: {
audioMuted: boolean | undefined;
audioOnlyEnabled: boolean | undefined;
skipPrejoin: boolean | undefined;
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
locationURL: URL): object => {
tenant: string | undefined): object => {
const state = {
room: roomName,
roomSafe: getBackendSafeRoomName(roomName),
tenant
};
const {
audioMuted = false,
audioOnlyEnabled = false,
skipPrejoin = false,
videoMuted = false
} = options;
if (audioMuted) {
// @ts-ignore
state['config.startWithAudioMuted'] = true;
}
if (audioOnlyEnabled) {
// @ts-ignore
state['config.startAudioOnly'] = true;
}
if (skipPrejoin) {
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
// @ts-ignore
state['config.prejoinConfig.enabled'] = false;
}
if (videoMuted) {
// @ts-ignore
state['config.startWithVideoMuted'] = true;
}
const params = new URLSearchParams(locationURL.hash);
for (const [ key, value ] of params) {

View File

@@ -14,10 +14,15 @@ export * from './functions.any';
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
* @param {URL} locationURL - The location URL.
* @param {Object} options: - Config options {
* audioMuted: boolean | undefined
* audioOnlyEnabled: boolean | undefined,
* skipPrejoin: boolean | undefined,
* videoMuted: boolean | undefined
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
@@ -25,11 +30,23 @@ export * from './functions.any';
*/
export const getTokenAuthUrl = (
config: IConfig,
locationURL: URL,
options: {
audioMuted: boolean | undefined;
audioOnlyEnabled: boolean | undefined;
skipPrejoin: boolean | undefined;
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
// eslint-disable-next-line max-params
locationURL: URL): Promise<string | undefined> => {
tenant: string | undefined): Promise<string | undefined> => {
const {
audioMuted = false,
audioOnlyEnabled = false,
skipPrejoin = false,
videoMuted = false
} = options;
let url = config.tokenAuthUrl;
@@ -38,7 +55,17 @@ export const getTokenAuthUrl = (
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin, locationURL);
const state = _getTokenAuthState(
locationURL,
{
audioMuted,
audioOnlyEnabled,
skipPrejoin,
videoMuted
},
roomName,
tenant
);
// Append ios=true or android=true to the token URL.
// @ts-ignore

View File

@@ -32,10 +32,15 @@ function _cryptoRandom() {
* argument to this method.
*
* @param {Object} config - Configuration state object from store. A URL pattern pointing to the login service.
* @param {string} roomName - The name of the conference room for which the user will be authenticated.
* @param {string} tenant - The name of the conference tenant.
* @param {string} skipPrejoin - The name of the conference room for which the user will be authenticated.
* @param {URL} locationURL - The current location URL.
* @param {URL} locationURL - The location URL.
* @param {Object} options: - Config options {
* audioMuted: boolean | undefined
* audioOnlyEnabled: boolean | undefined,
* skipPrejoin: boolean | undefined,
* videoMuted: boolean | undefined
* }.
* @param {string?} roomName - The room name.
* @param {string?} tenant - The tenant name if any.
*
* @returns {Promise<string|undefined>} - The URL pointing to JWT login service or
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
@@ -43,11 +48,23 @@ function _cryptoRandom() {
*/
export const getTokenAuthUrl = (
config: IConfig,
locationURL: URL,
options: {
audioMuted: boolean | undefined;
audioOnlyEnabled: boolean | undefined;
skipPrejoin: boolean | undefined;
videoMuted: boolean | undefined;
},
roomName: string | undefined,
tenant: string | undefined,
skipPrejoin: boolean | undefined = false,
// eslint-disable-next-line max-params
locationURL: URL): Promise<string | undefined> => {
tenant: string | undefined): Promise<string | undefined> => {
const {
audioMuted = false,
audioOnlyEnabled = false,
skipPrejoin = false,
videoMuted = false
} = options;
let url = config.tokenAuthUrl;
@@ -56,7 +73,17 @@ export const getTokenAuthUrl = (
}
if (url.indexOf('{state}')) {
const state = _getTokenAuthState(roomName, tenant, skipPrejoin, locationURL);
const state = _getTokenAuthState(
locationURL,
{
audioMuted,
audioOnlyEnabled,
skipPrejoin,
videoMuted
},
roomName,
tenant
);
if (browser.isElectron()) {
// @ts-ignore

View File

@@ -13,7 +13,9 @@ import {
JitsiConferenceErrors,
JitsiConnectionErrors
} from '../base/lib-jitsi-meet';
import { MEDIA_TYPE } from '../base/media/constants';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { isLocalTrackMuted } from '../base/tracks/functions.any';
import { parseURIString } from '../base/util/uri';
import { openLogoutDialog } from '../settings/actions';
@@ -257,6 +259,9 @@ function _handleLogin({ dispatch, getState }: IStore) {
const room = state['features/base/conference'].room;
const { locationURL = { href: '' } as URL } = state['features/base/connection'];
const { tenant } = parseURIString(locationURL.href) || {};
const { enabled: audioOnlyEnabled } = state['features/base/audio-only'];
const audioMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.AUDIO);
const videoMuted = isLocalTrackMuted(state['features/base/tracks'], MEDIA_TYPE.VIDEO);
if (!room) {
logger.warn('Cannot handle login, room is undefined!');
@@ -270,7 +275,18 @@ function _handleLogin({ dispatch, getState }: IStore) {
return;
}
getTokenAuthUrl(config, room, tenant, true, locationURL)
getTokenAuthUrl(
config,
locationURL,
{
audioMuted,
audioOnlyEnabled,
skipPrejoin: true,
videoMuted
},
room,
tenant
)
.then((tokenAuthServiceUrl: string | undefined) => {
if (!tokenAuthServiceUrl) {
logger.warn('Cannot handle login, token service URL is not set');

View File

@@ -90,6 +90,12 @@ export const HELP_BUTTON_ENABLED = 'help.enabled';
*/
export const INVITE_ENABLED = 'invite.enabled';
/**
* Flag indicating if dial-in invite functionality should be enabled.
* Default: enabled (true).
*/
export const INVITE_DIAL_IN_ENABLED = 'invite-dial-in.enabled';
/**
* Flag indicating if recording should be enabled in iOS.
* Default: disabled (false).

View File

@@ -4,7 +4,8 @@ import {
Keyboard,
KeyboardAvoidingView,
Platform,
StatusBar
StatusBar,
ViewStyle
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -38,9 +39,9 @@ interface IProps {
hasBottomTextInput: boolean;
/**
* Is the screen rendering a tab navigator?
* Is the screen header having an extra height?
*/
hasTabNavigator: boolean;
hasExtraHeaderHeight?: boolean;
/**
* Additional style to be appended to the KeyboardAvoidingView.
@@ -54,8 +55,8 @@ const JitsiKeyboardAvoidingView = (
children,
contentContainerStyle,
disableForcedKeyboardDismiss,
hasTabNavigator,
hasBottomTextInput,
hasExtraHeaderHeight,
style
}: IProps) => {
const headerHeight = useHeaderHeight();
@@ -69,13 +70,13 @@ const JitsiKeyboardAvoidingView = (
}, [ insets.bottom ]);
const tabNavigatorPadding
= hasTabNavigator ? headerHeight : 0;
const extraHeaderHeight
= hasExtraHeaderHeight ? headerHeight : 0;
const extraBottomPadding
= addBottomPadding ? bottomPadding : 0;
const noNotchDevicePadding = extraBottomPadding || 10;
const iosVerticalOffset
= headerHeight + noNotchDevicePadding + tabNavigatorPadding;
= headerHeight + noNotchDevicePadding + extraHeaderHeight;
const androidVerticalOffset = hasBottomTextInput
? headerHeight + Number(StatusBar.currentHeight) : headerHeight;
@@ -86,7 +87,7 @@ const JitsiKeyboardAvoidingView = (
return (
<KeyboardAvoidingView
behavior = { Platform.OS === 'ios' ? 'padding' : 'height' }
contentContainerStyle = { contentContainerStyle }
contentContainerStyle = { contentContainerStyle as ViewStyle }
enabled = { true }
keyboardVerticalOffset = {
Platform.OS === 'ios'
@@ -95,7 +96,7 @@ const JitsiKeyboardAvoidingView = (
}
onResponderRelease = { onRelease }
onStartShouldSetResponder = { shouldSetResponse }
style = { style }>
style = { style as ViewStyle }>
{ children }
</KeyboardAvoidingView>
);

View File

@@ -40,9 +40,9 @@ interface IProps {
hasBottomTextInput?: boolean;
/**
* Is the screen rendering a tab navigator?
* Is the screen header having an extra height?
*/
hasTabNavigator?: boolean;
hasExtraHeaderHeight?: boolean;
/**
* Insets for the SafeAreaView.
@@ -61,8 +61,8 @@ const JitsiScreen = ({
children,
disableForcedKeyboardDismiss = false,
footerComponent,
hasTabNavigator = false,
hasBottomTextInput = false,
hasExtraHeaderHeight = false,
safeAreaInsets = [ 'left', 'right' ],
style
}: IProps) => {
@@ -72,7 +72,7 @@ const JitsiScreen = ({
contentContainerStyle = { contentContainerStyle }
disableForcedKeyboardDismiss = { disableForcedKeyboardDismiss }
hasBottomTextInput = { hasBottomTextInput }
hasTabNavigator = { hasTabNavigator }
hasExtraHeaderHeight = { hasExtraHeaderHeight }
style = { style }>
<SafeAreaView
edges = { safeAreaInsets }

View File

@@ -11,7 +11,6 @@ const button = {
const buttonLabel = {
...BaseTheme.typography.bodyShortBold,
lineHeight: 14,
textTransform: 'capitalize'
};

View File

@@ -65,7 +65,7 @@ class Chat extends Component<IProps> {
<ChatInputBar onSend = { this._onSendMessage } />
}
hasBottomTextInput = { true }
hasTabNavigator = { true }
hasExtraHeaderHeight = { true }
style = { styles.chatContainer }>
{/* @ts-ignore */}
<MessageContainer messages = { _messages } />

View File

@@ -12,7 +12,7 @@ import {
import { EdgeInsets, withSafeAreaInsets } from 'react-native-safe-area-context';
import { connect, useDispatch } from 'react-redux';
import { appNavigate } from '../../../app/actions';
import { appNavigate } from '../../../app/actions.native';
import { IReduxState, IStore } from '../../../app/types';
import { CONFERENCE_BLURRED, CONFERENCE_FOCUSED } from '../../../base/conference/actionTypes';
import { FULLSCREEN_ENABLED, PIP_ENABLED } from '../../../base/flags/constants';
@@ -41,15 +41,15 @@ import { navigate } from '../../../mobile/navigation/components/conference/Confe
import { screen } from '../../../mobile/navigation/routes';
import { setPictureInPictureEnabled } from '../../../mobile/picture-in-picture/functions';
import Captions from '../../../subtitles/components/native/Captions';
import { setToolboxVisible } from '../../../toolbox/actions';
import { setToolboxVisible } from '../../../toolbox/actions.native';
import Toolbox from '../../../toolbox/components/native/Toolbox';
import { isToolboxVisible } from '../../../toolbox/functions';
import { isToolboxVisible } from '../../../toolbox/functions.native';
import {
AbstractConference,
abstractMapStateToProps
} from '../AbstractConference';
import type { AbstractProps } from '../AbstractConference';
import { isConnecting } from '../functions';
import { isConnecting } from '../functions.native';
import AlwaysOnLabels from './AlwaysOnLabels';
import ExpandedLabelPopup from './ExpandedLabelPopup';
@@ -230,7 +230,9 @@ class Conference extends AbstractConference<IProps, State> {
*/
componentDidUpdate(prevProps: IProps) {
const {
_showLobby
_audioOnlyEnabled,
_showLobby,
_startCarMode
} = this.props;
if (!prevProps._showLobby && _showLobby) {
@@ -238,6 +240,10 @@ class Conference extends AbstractConference<IProps, State> {
}
if (prevProps._showLobby && !_showLobby) {
if (_audioOnlyEnabled && _startCarMode) {
return;
}
navigate(screen.conference.main);
}
}

View File

@@ -34,6 +34,7 @@ import {
showSharedVideoMenu
} from '../../../participants-pane/actions.native';
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
import { shouldDisplayTileView } from '../../../video-layout/functions.native';
import { SQUARE_TILE_ASPECT_RATIO } from '../../constants';
import AudioMutedIndicator from './AudioMutedIndicator';
@@ -43,6 +44,7 @@ import RaisedHandIndicator from './RaisedHandIndicator';
import ScreenShareIndicator from './ScreenShareIndicator';
import styles, { AVATAR_SIZE } from './styles';
/**
* Thumbnail component's property types.
*/
@@ -111,6 +113,8 @@ interface IProps {
*/
_renderModeratorIndicator: boolean;
_shouldDisplayTileView: boolean;
/**
* The video track that will be displayed in the thumbnail.
*/
@@ -206,14 +210,25 @@ class Thumbnail extends PureComponent<IProps> {
_fakeParticipant,
_isScreenShare: isScreenShare,
_isVirtualScreenshare,
_renderModeratorIndicator: renderModeratorIndicator,
_participantId: participantId,
_pinned,
_renderModeratorIndicator: renderModeratorIndicator,
_shouldDisplayTileView,
renderDisplayName,
tileView
} = this.props;
const indicators = [];
let bottomIndicatorsContainerStyle;
if (_shouldDisplayTileView) {
bottomIndicatorsContainerStyle = styles.bottomIndicatorsContainer;
} else if (audioMuted || renderModeratorIndicator) {
bottomIndicatorsContainerStyle = styles.bottomIndicatorsContainer;
} else {
bottomIndicatorsContainerStyle = null;
}
if (!_fakeParticipant || _isVirtualScreenshare) {
indicators.push(<View
key = 'top-left-indicators'
@@ -228,10 +243,9 @@ class Thumbnail extends PureComponent<IProps> {
</View>);
indicators.push(<Container
key = 'bottom-indicators'
style = { styles.thumbnailIndicatorContainer }>
style = { styles.thumbnailIndicatorContainer as StyleType }>
<Container
style = { ((audioMuted || renderModeratorIndicator) && styles.bottomIndicatorsContainer
) as StyleType }>
style = { bottomIndicatorsContainerStyle as StyleType }>
{ audioMuted && !_isVirtualScreenshare && <AudioMutedIndicator /> }
{ !tileView && _pinned && <PinnedIndicator />}
{ renderModeratorIndicator && !_isVirtualScreenshare && <ModeratorIndicator />}
@@ -422,6 +436,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
_raisedHand: hasRaisedHand(participant),
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
_renderModeratorIndicator: renderModeratorIndicator,
_shouldDisplayTileView: shouldDisplayTileView(state),
_videoTrack: videoTrack
};
}

View File

@@ -144,8 +144,8 @@ export default {
},
bottomIndicatorsContainer: {
padding: 2,
flexDirection: 'row'
flexDirection: 'row',
padding: BaseTheme.spacing[1]
},
thumbnailTopLeftIndicatorContainer: {

View File

@@ -4,6 +4,7 @@ import { WithTranslation } from 'react-i18next';
import {
ActivityIndicator,
FlatList,
SafeAreaView,
TouchableOpacity,
View,
ViewStyle
@@ -199,6 +200,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
return (
<JitsiScreen
footerComponent = { this._renderShareMeetingButton }
hasExtraHeaderHeight = { true }
style = { styles.addPeopleContainer }>
<Input
autoFocus = { false }
@@ -497,7 +499,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
*/
_renderShareMeetingButton() {
return (
<View
<SafeAreaView
style = { [
styles.bottomBar as ViewStyle,
this.state.bottomPadding ? styles.extraBarPadding : null
@@ -508,7 +510,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
src = { IconShare }
style = { styles.shareIcon } />
</TouchableOpacity>
</View>
</SafeAreaView>
);
}

View File

@@ -29,8 +29,7 @@ export default {
bottomBar: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: BaseTheme.palette.ui01,
height: BaseTheme.spacing[10]
backgroundColor: BaseTheme.palette.ui01
},
clearButton: {

View File

@@ -563,12 +563,13 @@ export function searchDirectory( // eslint-disable-line max-params
* @param {IReduxState} state - The current state.
* @param {string} inviteUrl - The conference/location URL.
* @param {boolean} useHtml - Whether to return html text.
* @param {boolean} skipDialIn - Whether to skip dial-in options or not.
* @returns {Promise<string>} A {@code Promise} resolving with a
* descriptive text that can be used to invite participants to a meeting.
*/
export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?: boolean): Promise<string> {
export function getShareInfoText(
state: IReduxState, inviteUrl: string, useHtml?: boolean, skipDialIn?: boolean): Promise<string> {
let roomUrl = _decodeRoomURI(inviteUrl);
const includeDialInfo = state['features/base/config'] !== undefined;
if (useHtml) {
roomUrl = `<a href="${roomUrl}">${roomUrl}</a>`;
@@ -576,85 +577,72 @@ export function getShareInfoText(state: IReduxState, inviteUrl: string, useHtml?
let infoText = i18next.t('share.mainText', { roomUrl });
if (includeDialInfo) {
const { room } = parseURIString(inviteUrl);
let numbersPromise;
let hasPaymentError = false;
const { room } = parseURIString(inviteUrl);
const { dialInConfCodeUrl, dialInNumbersUrl, hosts } = state['features/base/config'];
const { locationURL = {} } = state['features/base/connection'];
const mucURL = hosts?.muc;
if (state['features/invite'].numbers
&& state['features/invite'].conferenceID) {
numbersPromise = Promise.resolve(state['features/invite']);
} else {
// we are requesting numbers and conferenceId directly
// not using updateDialInNumbers, because custom room
// is specified and we do not want to store the data
// in the state
const { dialInConfCodeUrl, dialInNumbersUrl, hosts }
= state['features/base/config'];
const { locationURL = {} } = state['features/base/connection'];
const mucURL = hosts?.muc;
if (!dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
// URLs for fetching dial in numbers not defined
return Promise.resolve(infoText);
}
numbersPromise = Promise.all([
getDialInNumbers(dialInNumbersUrl, room, mucURL), // @ts-ignore
getDialInConferenceID(dialInConfCodeUrl, room, mucURL, locationURL)
]).then(([ numbers, {
conference, id, message } ]) => {
if (!conference || !id) {
return Promise.reject(message);
}
return {
numbers,
conferenceID: id
};
});
}
return numbersPromise.then(
({ conferenceID, numbers }) => {
const phoneNumber = _getDefaultPhoneNumber(numbers) || '';
return `${
i18next.t('info.dialInNumber')} ${
phoneNumber} ${
i18next.t('info.dialInConferenceID')} ${
conferenceID}#\n\n`;
})
.catch(error => {
logger.error('Error fetching numbers or conferenceID', error);
hasPaymentError = error?.status === StatusCode.PaymentRequired;
})
.then(defaultDialInNumber => {
if (hasPaymentError) {
infoText += `${
i18next.t('info.dialInNumber')} ${i18next.t('info.reachedLimit')} ${
i18next.t('info.upgradeOptions')} ${UPGRADE_OPTIONS_TEXT}`;
return infoText;
}
let dialInfoPageUrl = getDialInfoPageURL(state, room);
if (useHtml) {
dialInfoPageUrl
= `<a href="${dialInfoPageUrl}">${dialInfoPageUrl}</a>`;
}
infoText += i18next.t('share.dialInfoText', {
defaultDialInNumber,
dialInfoPageUrl });
return infoText;
});
if (skipDialIn || !dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
// URLs for fetching dial in numbers not defined.
return Promise.resolve(infoText);
}
return Promise.resolve(infoText);
let hasPaymentError = false;
// We are requesting numbers and conferenceId directly
// not using updateDialInNumbers, because custom room
// is specified and we do not want to store the data
// in the state.
const numbersPromise = Promise.all([
getDialInNumbers(dialInNumbersUrl, room, mucURL), // @ts-ignore
getDialInConferenceID(dialInConfCodeUrl, room, mucURL, locationURL)
]).then(([ numbers, {
conference, id, message } ]) => {
if (!conference || !id) {
return Promise.reject(message);
}
return {
numbers,
conferenceID: id
};
});
return numbersPromise.then(({ conferenceID, numbers }) => {
const phoneNumber = _getDefaultPhoneNumber(numbers) || '';
return `${
i18next.t('info.dialInNumber')} ${
phoneNumber} ${
i18next.t('info.dialInConferenceID')} ${
conferenceID}#\n\n`;
})
.catch(error => {
logger.error('Error fetching numbers or conferenceID', error);
hasPaymentError = error?.status === StatusCode.PaymentRequired;
})
.then(defaultDialInNumber => {
if (hasPaymentError) {
infoText += `${
i18next.t('info.dialInNumber')} ${i18next.t('info.reachedLimit')} ${
i18next.t('info.upgradeOptions')} ${UPGRADE_OPTIONS_TEXT}`;
return infoText;
}
let dialInfoPageUrl = getDialInfoPageURL(state, room);
if (useHtml) {
dialInfoPageUrl = `<a href="${dialInfoPageUrl}">${dialInfoPageUrl}</a>`;
}
infoText += i18next.t('share.dialInfoText', {
defaultDialInNumber,
dialInfoPageUrl });
return infoText;
});
}
/**

View File

@@ -29,7 +29,10 @@ class LobbyChatScreen extends
const { _lobbyChatMessages } = this.props;
return (
<JitsiScreen style = { styles.lobbyChatWrapper }>
<JitsiScreen
hasBottomTextInput = { true }
hasExtraHeaderHeight = { true }
style = { styles.lobbyChatWrapper }>
{/* @ts-ignore */}
<MessageContainer messages = { _lobbyChatMessages } />
<ChatInputBar onSend = { this._onSendMessage } />

View File

@@ -4,10 +4,7 @@ export default {
lobbyChatWrapper: {
backgroundColor: BaseTheme.palette.ui01,
alignItems: 'stretch',
flexDirection: 'column',
justifyItems: 'center',
height: '100%'
flex: 1
},
passwordJoinButtons: {

View File

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

View File

@@ -1,3 +1,5 @@
import { NativeModules, Platform } from 'react-native';
import { getAppProp } from '../../base/app/functions';
import {
CONFERENCE_BLURRED,
@@ -6,8 +8,10 @@ import {
CONFERENCE_LEFT,
CONFERENCE_WILL_JOIN
} from '../../base/conference/actionTypes';
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
import { PARTICIPANT_JOINED } from '../../base/participants/actionTypes';
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
import { READY_TO_CLOSE } from '../external-api/actionTypes';
import { participantToParticipantInfo } from '../external-api/functions';
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
@@ -15,9 +19,12 @@ import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
import { isExternalAPIAvailable } from './functions';
const externalAPIEnabled = isExternalAPIAvailable();
const { JMOngoingConference } = NativeModules;
/**
* Check if native modules are being used or not. If not then the init of middleware doesn't happen.
* Check if native modules are being used or not.
* If not, then the init of middleware doesn't happen.
*/
!externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
const result = next(action);
@@ -25,6 +32,12 @@ const externalAPIEnabled = isExternalAPIAvailable();
const rnSdkHandlers = getAppProp(store, 'rnSdkHandlers');
switch (type) {
case SET_AUDIO_MUTED:
rnSdkHandlers?.onAudioMutedChanged && rnSdkHandlers?.onAudioMutedChanged(action.muted);
break;
case SET_VIDEO_MUTED:
rnSdkHandlers?.onVideoMutedChanged && rnSdkHandlers?.onVideoMutedChanged(Boolean(action.muted));
break;
case CONFERENCE_BLURRED:
rnSdkHandlers?.onConferenceBlurred && rnSdkHandlers?.onConferenceBlurred();
break;
@@ -56,5 +69,22 @@ const externalAPIEnabled = isExternalAPIAvailable();
}
return result;
}
});
/**
* Before enabling media projection service control on Android,
* we need to check if native modules are being used or not.
*/
Platform.OS === 'android' && !externalAPIEnabled && StateListenerRegistry.register(
state => state['features/base/conference'].conference,
(conference, previousConference) => {
if (!conference) {
JMOngoingConference.abort();
} else if (conference && !previousConference) {
JMOngoingConference.launch();
} else if (conference !== previousConference) {
JMOngoingConference.abort();
JMOngoingConference.launch();
}
}
);

View File

@@ -288,6 +288,6 @@ export default {
visitorsLabel: {
...BaseTheme.typography.heading6,
color: BaseTheme.palette.warning02,
marginLeft: BaseTheme.spacing[3]
marginLeft: BaseTheme.spacing[2]
}
};

View File

@@ -1,8 +1,6 @@
import React, { useCallback } from 'react';
import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
import { useSelector } from 'react-redux';
import { getLocalParticipant } from '../../../base/participants/functions';
import Button from '../../../base/ui/components/native/Button';
import { BUTTON_TYPES } from '../../../base/ui/constants.native';
import AbstractPollResults from '../AbstractPollResults';
@@ -20,6 +18,7 @@ const PollResults = (props: AbstractProps) => {
const {
answers,
changeVote,
creatorName,
haveVoted,
question,
showDetails,
@@ -88,7 +87,6 @@ const PollResults = (props: AbstractProps) => {
);
}, [ showDetails ]);
const localParticipant = useSelector(getLocalParticipant);
/* eslint-disable react/jsx-no-bind */
@@ -96,7 +94,7 @@ const PollResults = (props: AbstractProps) => {
<View>
<Text style = { dialogStyles.questionText as TextStyle } >{ question }</Text>
<Text style = { dialogStyles.questionOwnerText as TextStyle } >
{ t('polls.by', { name: localParticipant?.name }) }
{ t('polls.by', { name: creatorName }) }
</Text>
<FlatList
data = { answers }

View File

@@ -48,7 +48,7 @@ const PollsPane = (props: AbstractProps) => {
<JitsiScreen
contentContainerStyle = { chatStyles.pollPane as StyleType }
disableForcedKeyboardDismiss = { true }
hasTabNavigator = { true }
hasExtraHeaderHeight = { true }
style = { chatStyles.pollPaneContainer as StyleType }>
{
createMode

View File

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

View File

@@ -17,6 +17,11 @@ interface IProps {
*/
_password?: string;
/**
* Number of digits used in the room-lock password.
*/
_passwordNumberOfDigits?: number;
/**
* The {@code JitsiConference} which requires a password.
*
@@ -88,6 +93,15 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
*/
render() {
const { password } = this.state;
const { _passwordNumberOfDigits } = this.props;
const textInputProps: any = {
secureTextEntry: true
};
if (_passwordNumberOfDigits) {
textInputProps.keyboardType = 'numeric';
textInputProps.maxLength = _passwordNumberOfDigits;
}
return (
<InputDialog
@@ -96,9 +110,7 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
messageKey = { password ? 'dialog.incorrectRoomLockPassword' : undefined }
onCancel = { this._onCancel }
onSubmit = { this._onSubmit }
textInputProps = {{
secureTextEntry: true
}}
textInputProps = { textInputProps }
titleKey = 'dialog.password' />
);
}
@@ -142,8 +154,11 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
const { roomPasswordNumberOfDigits } = state['features/base/config'];
return {
_password: state['features/base/conference'].password
_password: state['features/base/conference'].password,
_passwordNumberOfDigits: roomPasswordNumberOfDigits
};
}

View File

@@ -56,7 +56,7 @@ interface IProps {
_isModerator: boolean;
/**
* State of the lobby mode.
* Whether lobby mode is enabled or not.
*/
_lobbyEnabled: boolean;
@@ -107,6 +107,11 @@ interface IProps {
*/
interface IState {
/**
* State of lobby mode.
*/
lobbyEnabled: boolean;
/**
* Password added by the participant for room lock.
*/
@@ -134,6 +139,7 @@ class SecurityDialog extends PureComponent<IProps, IState> {
super(props);
this.state = {
lobbyEnabled: props._lobbyEnabled,
passwordInputValue: '',
showElement: props._locked === LOCKED_LOCALLY || false
};
@@ -168,7 +174,6 @@ class SecurityDialog extends PureComponent<IProps, IState> {
*/
_renderLobbyMode() {
const {
_lobbyEnabled,
_lobbyModeSwitchVisible,
t
} = this.props;
@@ -188,7 +193,7 @@ class SecurityDialog extends PureComponent<IProps, IState> {
{ t('lobby.toggleLabel') }
</Text>
<Switch
checked = { _lobbyEnabled }
checked = { this.state.lobbyEnabled }
onChange = { this._onToggleLobbyMode } />
</View>
</View>
@@ -386,13 +391,14 @@ class SecurityDialog extends PureComponent<IProps, IState> {
* @returns {void}
*/
_onToggleLobbyMode() {
const { _lobbyEnabled, dispatch } = this.props;
const { dispatch } = this.props;
const { lobbyEnabled } = this.state;
if (_lobbyEnabled) {
dispatch(toggleLobbyMode(false));
} else {
dispatch(toggleLobbyMode(true));
}
this.setState({
lobbyEnabled: !lobbyEnabled
});
dispatch(toggleLobbyMode(!lobbyEnabled));
}
/**

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
@@ -7,7 +7,7 @@ import { IReduxState } from '../../../app/types';
import { updateSettings } from '../../../base/settings/actions';
import Input from '../../../base/ui/components/native/Input';
import Switch from '../../../base/ui/components/native/Switch';
import { isServerURLChangeEnabled, normalizeUserInputURL } from '../../functions.any';
import { isServerURLChangeEnabled, normalizeUserInputURL } from '../../functions.native';
import FormRow from './FormRow';
import FormSection from './FormSection';
@@ -17,7 +17,6 @@ import styles from './styles';
const ConferenceSection = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const defaultServerURL = useSelector((state: IReduxState) => getDefaultURL(state));
const {
serverURL,
startCarMode,
@@ -25,7 +24,10 @@ const ConferenceSection = () => {
startWithVideoMuted
} = useSelector((state: IReduxState) => state['features/base/settings']);
const { serverURLChangeEnabled } = useSelector((state: IReduxState) => isServerURLChangeEnabled(state));
const defaultServerURL = useSelector((state: IReduxState) => getDefaultURL(state));
const [ newServerURL, setNewServerURL ] = useState(serverURL ?? '');
const serverURLChangeEnabled = useSelector((state: IReduxState) => isServerURLChangeEnabled(state));
const switches = useMemo(() => [
{
@@ -45,21 +47,27 @@ const ConferenceSection = () => {
}
], [ startCarMode, startWithAudioMuted, startWithVideoMuted ]);
const onChangeServerURL = useCallback(newServerURL => {
dispatch(updateSettings({ serverURL: newServerURL }));
}, [ updateSettings ]);
const onChangeServerURL = useCallback(value => {
setNewServerURL(value);
dispatch(updateSettings({
serverURL: value
}));
}, [ dispatch, newServerURL ]);
const processServerURL = useCallback(() => {
const normalizedURL = normalizeUserInputURL(serverURL ?? '');
const normalizedURL = normalizeUserInputURL(newServerURL);
onChangeServerURL(normalizedURL);
}, [ serverURL ]);
}, [ newServerURL ]);
useEffect(() => () => processServerURL(), []);
const onSwitchToggled = useCallback((name: string) => (enabled?: boolean) => {
// @ts-ignore
dispatch(updateSettings({ [name]: enabled }));
}, [ dispatch, updateSettings ]);
}, [ dispatch ]);
return (
<FormSection
@@ -74,7 +82,7 @@ const ConferenceSection = () => {
onChange = { onChangeServerURL }
placeholder = { defaultServerURL }
textContentType = { 'URL' } // iOS only
value = { serverURL ?? '' } />
value = { newServerURL } />
{
switches.map(({ label, state, name }) => (
<FormRow

View File

@@ -2,6 +2,7 @@ import { useNavigation } from '@react-navigation/native';
import React, { useCallback, useLayoutEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, Text, View, ViewStyle } from 'react-native';
import { Edge } from 'react-native-safe-area-context';
import { useDispatch, useSelector } from 'react-redux';
import { IReduxState } from '../../../app/types';
@@ -39,6 +40,7 @@ const ProfileView = ({ isInWelcomePage }: {
(state: IReduxState) => state['features/base/settings']
);
const participant = useSelector((state: IReduxState) => getLocalParticipant(state));
const { locationURL } = useSelector((state: IReduxState) => state['features/base/connection']);
const [ displayName, setDisplayName ] = useState(reduxDisplayName);
const [ email, setEmail ] = useState(reduxEmail);
@@ -103,7 +105,9 @@ const ProfileView = ({ isInWelcomePage }: {
useLayoutEffect(() => {
navigation.setOptions({
headerLeft,
headerRight: !isInWelcomePage && headerRight
headerRight: !isInWelcomePage
&& !locationURL?.hostname?.includes('8x8.vc')
&& headerRight
});
}, [ navigation ]);
@@ -112,7 +116,7 @@ const ProfileView = ({ isInWelcomePage }: {
disableForcedKeyboardDismiss = { true }
// @ts-ignore
safeAreaInsets = { [ !isInWelcomePage && 'bottom', 'left', 'right' ].filter(Boolean) }
safeAreaInsets = { [ !isInWelcomePage && 'bottom', 'left', 'right' ].filter(Boolean) as Edge[] }
style = { styles.settingsViewContainer }>
<ScrollView
bounces = { isInWelcomePage }

View File

@@ -2,6 +2,8 @@ import { Share } from 'react-native';
import { getName } from '../app/functions.native';
import { IStore } from '../app/types';
import { INVITE_DIAL_IN_ENABLED } from '../base/flags/constants';
import { getFeatureFlag } from '../base/flags/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { getShareInfoText } from '../invite/functions';
@@ -35,7 +37,9 @@ MiddlewareRegistry.register(store => next => action => {
* @returns {void}
*/
function _shareRoom(roomURL: string, { dispatch, getState }: IStore) {
getShareInfoText(getState(), roomURL)
const dialInEnabled = getFeatureFlag(getState(), INVITE_DIAL_IN_ENABLED, true);
getShareInfoText(getState(), roomURL, false /* useHtml */, !dialInEnabled /* skipDialIn */)
.then(message => {
const title = `${getName()} Conference`;
const onFulfilled
@@ -53,14 +57,15 @@ function _shareRoom(roomURL: string, { dispatch, getState }: IStore) {
.then(
/* onFulfilled */ value => {
onFulfilled(value.action === Share.sharedAction);
dispatch(toggleShareDialog(false));
},
/* onRejected */ reason => {
dispatch(toggleShareDialog(false));
logger.error(
`Failed to share conference/room URL ${roomURL}:`,
reason);
onFulfilled(false);
});
})
.finally(() => {
dispatch(toggleShareDialog(false));
});
});
}