mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-04 13:52:28 +00:00
Compare commits
57 Commits
saghul-pat
...
8420
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc98fc4839 | ||
|
|
a815f97c7e | ||
|
|
8261cf2811 | ||
|
|
f2238935b5 | ||
|
|
5f12f76ada | ||
|
|
5a9464697f | ||
|
|
f44601a82b | ||
|
|
3d3de4a884 | ||
|
|
c02ad56b6d | ||
|
|
ea7c5ccd58 | ||
|
|
7ec3eae72b | ||
|
|
edf7d18308 | ||
|
|
6bf4a4e91d | ||
|
|
5fd966f042 | ||
|
|
e275f20055 | ||
|
|
ff624a34d8 | ||
|
|
c98050224c | ||
|
|
5bee373091 | ||
|
|
db4ab34ddf | ||
|
|
ef138fb5aa | ||
|
|
13bfdaed68 | ||
|
|
ff656f4e6b | ||
|
|
a27b78cef0 | ||
|
|
4fa426ace0 | ||
|
|
ac34f524fa | ||
|
|
31a4f2a4ec | ||
|
|
dc908512f9 | ||
|
|
ae983645d1 | ||
|
|
3514b22191 | ||
|
|
405af3af5f | ||
|
|
a6d333c07a | ||
|
|
0387cdc888 | ||
|
|
f670f39dd2 | ||
|
|
7262465777 | ||
|
|
75b4049529 | ||
|
|
ac6185424c | ||
|
|
9e15df8e3d | ||
|
|
83f83c17eb | ||
|
|
3e1adcd9b7 | ||
|
|
8105127571 | ||
|
|
bc99a72984 | ||
|
|
e10eaaa3d9 | ||
|
|
0e831074c0 | ||
|
|
326b694bf2 | ||
|
|
9e1f3de4e5 | ||
|
|
07a25a1f00 | ||
|
|
d6bbe07cf2 | ||
|
|
e9a8fd5392 | ||
|
|
aea9c5e79e | ||
|
|
b60210d0ad | ||
|
|
f0d2106c1a | ||
|
|
13f1cb13c5 | ||
|
|
c27ca779ab | ||
|
|
aedb43ec5b | ||
|
|
0a68eed294 | ||
|
|
3f51b10245 | ||
|
|
5260cd7e30 |
2
Makefile
2
Makefile
@@ -48,8 +48,6 @@ deploy-appbundle:
|
||||
$(BUILD_DIR)/external_api.min.js.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.js.map \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js.map \
|
||||
$(BUILD_DIR)/face-landmarks-worker.min.js \
|
||||
$(BUILD_DIR)/face-landmarks-worker.min.js.map \
|
||||
$(BUILD_DIR)/noise-suppressor-worklet.min.js \
|
||||
|
||||
@@ -42,7 +42,7 @@ ext {
|
||||
googleServicesEnabled = project.file('app/google-services.json').exists() && !libreBuild
|
||||
|
||||
//React Native and Hermes Version
|
||||
rnVersion = "0.75.4"
|
||||
rnVersion = "0.75.5"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -69,10 +69,29 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
// Due to a dependency conflict between React Native and the Fresco library used by GiphySDK,
|
||||
// GIFs appear as static images instead of animating
|
||||
// https://github.com/Giphy/giphy-react-native-sdk/commit/7fe466ed6fddfaec95f9cbc959d33bd75ad8f900
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy {
|
||||
forcedModules = [
|
||||
'com.facebook.fresco:fresco:3.2.0',
|
||||
'com.facebook.fresco:animated-gif:3.2.0',
|
||||
'com.facebook.fresco:animated-base:3.2.0',
|
||||
'com.facebook.fresco:animated-drawable:3.2.0',
|
||||
'com.facebook.fresco:animated-webp:3.2.0',
|
||||
'com.facebook.fresco:webpsupport:3.2.0',
|
||||
'com.facebook.fresco:imagepipeline-okhttp3:3.2.0',
|
||||
'com.facebook.fresco:middleware:3.2.0',
|
||||
'com.facebook.fresco:nativeimagetranscoder:3.2.0'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Third-party react-native modules which Jitsi Meet SDK for Android depends
|
||||
// on and which are not available in third-party Maven repositories need to
|
||||
// be deployed in a Maven repository of ours.
|
||||
//
|
||||
|
||||
if (project.name.startsWith('react-native-')) {
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
@@ -78,7 +78,11 @@ public class BroadcastAction {
|
||||
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
|
||||
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED"),
|
||||
SET_CLOSED_CAPTIONS_ENABLED("org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED"),
|
||||
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA");
|
||||
TOGGLE_CAMERA("org.jitsi.meet.TOGGLE_CAMERA"),
|
||||
SHOW_NOTIFICATION("org.jitsi.meet.SHOW_NOTIFICATION"),
|
||||
HIDE_NOTIFICATION("org.jitsi.meet.HIDE_NOTIFICATION"),
|
||||
START_RECORDING("org.jitsi.meet.START_RECORDING"),
|
||||
STOP_RECORDING("org.jitsi.meet.STOP_RECORDING");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public class BroadcastEvent {
|
||||
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED"),
|
||||
READY_TO_CLOSE("org.jitsi.meet.READY_TO_CLOSE"),
|
||||
TRANSCRIPTION_CHUNK_RECEIVED("org.jitsi.meet.TRANSCRIPTION_CHUNK_RECEIVED"),
|
||||
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED("org.jitsi.meet.CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED");
|
||||
CUSTOM_BUTTON_PRESSED("org.jitsi.meet.CUSTOM_BUTTON_PRESSED");
|
||||
|
||||
private static final String CONFERENCE_BLURRED_NAME = "CONFERENCE_BLURRED";
|
||||
private static final String CONFERENCE_FOCUSED_NAME = "CONFERENCE_FOCUSED";
|
||||
@@ -109,7 +109,7 @@ public class BroadcastEvent {
|
||||
private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
|
||||
private static final String READY_TO_CLOSE_NAME = "READY_TO_CLOSE";
|
||||
private static final String TRANSCRIPTION_CHUNK_RECEIVED_NAME = "TRANSCRIPTION_CHUNK_RECEIVED";
|
||||
private static final String CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME = "CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED";
|
||||
private static final String CUSTOM_BUTTON_PRESSED_NAME = "CUSTOM_BUTTON_PRESSED";
|
||||
|
||||
private final String action;
|
||||
|
||||
@@ -164,8 +164,8 @@ public class BroadcastEvent {
|
||||
return READY_TO_CLOSE;
|
||||
case TRANSCRIPTION_CHUNK_RECEIVED_NAME:
|
||||
return TRANSCRIPTION_CHUNK_RECEIVED;
|
||||
case CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED_NAME:
|
||||
return CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED;
|
||||
case CUSTOM_BUTTON_PRESSED_NAME:
|
||||
return CUSTOM_BUTTON_PRESSED;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.jitsi.meet.sdk;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class BroadcastIntentHelper {
|
||||
public static Intent buildSetAudioMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_AUDIO_MUTED.getAction());
|
||||
intent.putExtra("muted", muted);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -17,18 +19,21 @@ public class BroadcastIntentHelper {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_ENDPOINT_TEXT_MESSAGE.getAction());
|
||||
intent.putExtra("to", to);
|
||||
intent.putExtra("message", message);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildToggleScreenShareIntent(boolean enabled) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.TOGGLE_SCREEN_SHARE.getAction());
|
||||
intent.putExtra("enabled", enabled);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildOpenChatIntent(String participantId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@@ -40,28 +45,98 @@ public class BroadcastIntentHelper {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
intent.putExtra("to", participantId);
|
||||
intent.putExtra("message", message);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetVideoMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
intent.putExtra("muted", muted);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetClosedCaptionsEnabledIntent(boolean enabled) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
|
||||
intent.putExtra("enabled", enabled);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
|
||||
public static Intent buildRetrieveParticipantsInfo(String requestId) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.RETRIEVE_PARTICIPANTS_INFO.getAction());
|
||||
intent.putExtra("requestId", requestId);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildToggleCameraIntent() {
|
||||
return new Intent(BroadcastAction.Type.TOGGLE_CAMERA.getAction());
|
||||
}
|
||||
|
||||
public static Intent buildShowNotificationIntent(
|
||||
String appearance, String description, String timeout, String title, String uid) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SHOW_NOTIFICATION.getAction());
|
||||
intent.putExtra("appearance", appearance);
|
||||
intent.putExtra("description", description);
|
||||
intent.putExtra("timeout", timeout);
|
||||
intent.putExtra("title", title);
|
||||
intent.putExtra("uid", uid);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildHideNotificationIntent(String uid) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.HIDE_NOTIFICATION.getAction());
|
||||
intent.putExtra("uid", uid);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public enum RecordingMode {
|
||||
FILE("file"),
|
||||
STREAM("stream");
|
||||
|
||||
private final String mode;
|
||||
|
||||
RecordingMode(String mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
public static Intent buildStartRecordingIntent(
|
||||
RecordingMode mode,
|
||||
String dropboxToken,
|
||||
boolean shouldShare,
|
||||
String rtmpStreamKey,
|
||||
String rtmpBroadcastID,
|
||||
String youtubeStreamKey,
|
||||
String youtubeBroadcastID,
|
||||
Bundle extraMetadata,
|
||||
boolean transcription) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.START_RECORDING.getAction());
|
||||
intent.putExtra("mode", mode.getMode());
|
||||
intent.putExtra("dropboxToken", dropboxToken);
|
||||
intent.putExtra("shouldShare", shouldShare);
|
||||
intent.putExtra("rtmpStreamKey", rtmpStreamKey);
|
||||
intent.putExtra("rtmpBroadcastID", rtmpBroadcastID);
|
||||
intent.putExtra("youtubeStreamKey", youtubeStreamKey);
|
||||
intent.putExtra("youtubeBroadcastID", youtubeBroadcastID);
|
||||
intent.putExtra("extraMetadata", extraMetadata);
|
||||
intent.putExtra("transcription", transcription);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildStopRecordingIntent(RecordingMode mode, boolean transcription) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.STOP_RECORDING.getAction());
|
||||
intent.putExtra("mode", mode.getMode());
|
||||
intent.putExtra("transcription", transcription);
|
||||
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,10 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
|
||||
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
constants.put("SET_CLOSED_CAPTIONS_ENABLED", BroadcastAction.Type.SET_CLOSED_CAPTIONS_ENABLED.getAction());
|
||||
constants.put("TOGGLE_CAMERA", BroadcastAction.Type.TOGGLE_CAMERA.getAction());
|
||||
constants.put("SHOW_NOTIFICATION", BroadcastAction.Type.SHOW_NOTIFICATION.getAction());
|
||||
constants.put("HIDE_NOTIFICATION", BroadcastAction.Type.HIDE_NOTIFICATION.getAction());
|
||||
constants.put("START_RECORDING", BroadcastAction.Type.START_RECORDING.getAction());
|
||||
constants.put("STOP_RECORDING", BroadcastAction.Type.STOP_RECORDING.getAction());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
4
app.js
4
app.js
@@ -1,9 +1,5 @@
|
||||
/* Jitsi Meet app main entrypoint. */
|
||||
|
||||
// Polyfill Promise.withReolvers.
|
||||
// FIXME(saghul) webpack + core-js v3 should polyfill this.
|
||||
import 'promise.withresolvers/auto';
|
||||
|
||||
// Re-export jQuery
|
||||
// FIXME: Remove this requirement from torture tests.
|
||||
import $ from 'jquery';
|
||||
|
||||
@@ -172,7 +172,9 @@ let room;
|
||||
|
||||
/*
|
||||
* Logic to open a desktop picker put on the window global for
|
||||
* lib-jitsi-meet to detect and invoke
|
||||
* lib-jitsi-meet to detect and invoke.
|
||||
*
|
||||
* TODO: remove once the Electron SDK supporting gDM has been out for a while.
|
||||
*/
|
||||
window.JitsiMeetScreenObtainer = {
|
||||
openDesktopPicker(options, onSourceChoose) {
|
||||
@@ -287,7 +289,7 @@ class ConferenceConnector {
|
||||
},
|
||||
descriptionKey: 'dialog.reservationErrorMsg',
|
||||
titleKey: 'dialog.reservationError'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -295,7 +297,7 @@ class ConferenceConnector {
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.gracefulShutdown',
|
||||
titleKey: 'dialog.serviceUnavailable'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
break;
|
||||
|
||||
// FIXME FOCUS_DISCONNECTED is a confusing event name.
|
||||
|
||||
@@ -1091,9 +1091,6 @@ var config = {
|
||||
// True if the analytics should be disabled
|
||||
// disabled: false,
|
||||
|
||||
// The Google Analytics Tracking ID:
|
||||
// googleAnalyticsTrackingId: 'your-tracking-id-UA-123456-1',
|
||||
|
||||
// Matomo configuration:
|
||||
// matomoEndpoint: 'https://your-matomo-endpoint/',
|
||||
// matomoSiteID: '42',
|
||||
@@ -1131,7 +1128,6 @@ var config = {
|
||||
|
||||
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
|
||||
// scriptURLs: [
|
||||
// "libs/analytics-ga.min.js", // google-analytics
|
||||
// "https://example.com/my-custom-analytics.js",
|
||||
// ],
|
||||
|
||||
|
||||
20
ios/Podfile
20
ios/Podfile
@@ -78,8 +78,6 @@ target 'JitsiMeetSDKLite' do
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
||||
PLIST_BUDDY_PATH = '/usr/libexec/PlistBuddy'
|
||||
react_native_post_install(
|
||||
installer,
|
||||
use_native_modules![:reactNativePath],
|
||||
@@ -98,23 +96,5 @@ post_install do |installer|
|
||||
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1'
|
||||
config.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -no-verify-emitted-module-interface'
|
||||
end
|
||||
|
||||
# Can be removed when updated to RN 0.76
|
||||
# Issue https://github.com/facebook/react-native/issues/35863#issuecomment-1387465588
|
||||
if target.name == "hermes-engine"
|
||||
installer.pods_project.files.each do |fileref|
|
||||
if fileref.path.end_with? "hermes.xcframework"
|
||||
hermes_plist_file = "#{fileref.real_path}/Info.plist"
|
||||
# Patch Hermes to remove the debug symbols entry from the Info.plist (as it's not shipped with it)
|
||||
# This might be removed once Hermes starts to ship with Debug symbols or we remove our
|
||||
# direct dependency from the Main iOS target on "hermes.xcframework"
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:0:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:1:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:2:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:3:DebugSymbolsPath', hermes_plist_file)
|
||||
Open3.capture3(PLIST_BUDDY_PATH, '-c', 'Delete :AvailableLibraries:4:DebugSymbolsPath', hermes_plist_file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
524
ios/Podfile.lock
524
ios/Podfile.lock
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,10 @@
|
||||
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
JitsiMeet *jitsiMeet = [JitsiMeet sharedInstance];
|
||||
|
||||
#if 0
|
||||
jitsiMeet.webRtcLoggingSeverity = WebRTCLoggingSeverityVerbose;
|
||||
#endif
|
||||
|
||||
jitsiMeet.conferenceActivityType = JitsiMeetConferenceActivityType;
|
||||
jitsiMeet.customUrlScheme = @"org.jitsi.meet";
|
||||
jitsiMeet.universalLinkDomains = @[@"meet.jit.si", @"alpha.jitsi.net", @"beta.meet.jit.si"];
|
||||
@@ -36,7 +40,7 @@
|
||||
jitsiMeet.defaultConferenceOptions = [JitsiMeetConferenceOptions fromBuilder:^(JitsiMeetConferenceOptionsBuilder *builder) {
|
||||
|
||||
// For testing configOverrides a room needs to be set
|
||||
// builder.room = @"test0988test";
|
||||
// builder.room = @"https://meet.jit.si/test0988test";
|
||||
|
||||
[builder setFeatureFlag:@"welcomepage.enabled" withBoolean:YES];
|
||||
[builder setFeatureFlag:@"ios.screensharing.enabled" withBoolean:YES];
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
|
||||
static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
|
||||
typedef NS_ENUM(NSInteger, RecordingMode) {
|
||||
RecordingModeFile,
|
||||
RecordingModeStream
|
||||
};
|
||||
|
||||
@interface ExternalAPI : RCTEventEmitter<RCTBridgeModule>
|
||||
|
||||
- (void)sendHangUp;
|
||||
@@ -27,9 +32,13 @@ static NSString * const sendEventNotificationName = @"org.jitsi.meet.SendEvent";
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to;
|
||||
- (void)sendSetVideoMuted:(BOOL)muted;
|
||||
- (void)sendSetClosedCaptionsEnabled:(BOOL)enabled;
|
||||
- (void)toggleCamera;
|
||||
- (void)showNotification:(NSString*)appearance :(NSString*)description :(NSString*)timeout :(NSString*)title :(NSString*)uid;
|
||||
- (void)hideNotification:(NSString*)uid;
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString*)dropboxToken :(BOOL)shouldShare :(NSString*)rtmpStreamKey :(NSString*)rtmpBroadcastID :(NSString*)youtubeStreamKey :(NSString*)youtubeBroadcastID :(NSDictionary*)extraMetadata :(BOOL)transcription;
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription;
|
||||
|
||||
@end
|
||||
|
||||
@@ -28,6 +28,10 @@ static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSA
|
||||
static NSString * const setVideoMutedAction = @"org.jitsi.meet.SET_VIDEO_MUTED";
|
||||
static NSString * const setClosedCaptionsEnabledAction = @"org.jitsi.meet.SET_CLOSED_CAPTIONS_ENABLED";
|
||||
static NSString * const toggleCameraAction = @"org.jitsi.meet.TOGGLE_CAMERA";
|
||||
static NSString * const showNotificationAction = @"org.jitsi.meet.SHOW_NOTIFICATION";
|
||||
static NSString * const hideNotificationAction = @"org.jitsi.meet.HIDE_NOTIFICATION";
|
||||
static NSString * const startRecordingAction = @"org.jitsi.meet.START_RECORDING";
|
||||
static NSString * const stopRecordingAction = @"org.jitsi.meet.STOP_RECORDING";
|
||||
|
||||
@implementation ExternalAPI
|
||||
|
||||
@@ -52,7 +56,11 @@ RCT_EXPORT_MODULE();
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
|
||||
@"SET_VIDEO_MUTED" : setVideoMutedAction,
|
||||
@"SET_CLOSED_CAPTIONS_ENABLED": setClosedCaptionsEnabledAction,
|
||||
@"TOGGLE_CAMERA": toggleCameraAction
|
||||
@"TOGGLE_CAMERA": toggleCameraAction,
|
||||
@"SHOW_NOTIFICATION": showNotificationAction,
|
||||
@"HIDE_NOTIFICATION": hideNotificationAction,
|
||||
@"START_RECORDING": startRecordingAction,
|
||||
@"STOP_RECORDING": stopRecordingAction
|
||||
};
|
||||
};
|
||||
|
||||
@@ -78,7 +86,11 @@ RCT_EXPORT_MODULE();
|
||||
sendChatMessageAction,
|
||||
setVideoMutedAction,
|
||||
setClosedCaptionsEnabledAction,
|
||||
toggleCameraAction
|
||||
toggleCameraAction,
|
||||
showNotificationAction,
|
||||
hideNotificationAction,
|
||||
startRecordingAction,
|
||||
stopRecordingAction
|
||||
];
|
||||
}
|
||||
|
||||
@@ -180,4 +192,59 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:toggleCameraAction body:nil];
|
||||
}
|
||||
|
||||
- (void)showNotification:(NSString*)appearance :(NSString*)description :(NSString*)timeout :(NSString*)title :(NSString*)uid {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"appearance"] = appearance;
|
||||
data[@"description"] = description;
|
||||
data[@"timeout"] = timeout;
|
||||
data[@"title"] = title;
|
||||
data[@"uid"] = uid;
|
||||
|
||||
[self sendEventWithName:showNotificationAction body:data];
|
||||
}
|
||||
|
||||
- (void)hideNotification:(NSString*)uid {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"uid"] = uid;
|
||||
|
||||
[self sendEventWithName:hideNotificationAction body:data];
|
||||
}
|
||||
|
||||
static inline NSString *RecordingModeToString(RecordingMode mode) {
|
||||
switch (mode) {
|
||||
case RecordingModeFile:
|
||||
return @"file";
|
||||
case RecordingModeStream:
|
||||
return @"stream";
|
||||
default:
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString*)dropboxToken :(BOOL)shouldShare :(NSString*)rtmpStreamKey :(NSString*)rtmpBroadcastID :(NSString*)youtubeStreamKey :(NSString*)youtubeBroadcastID :(NSDictionary*)extraMetadata :(BOOL)transcription {
|
||||
NSString *modeString = RecordingModeToString(mode);
|
||||
NSDictionary *data = @{
|
||||
@"mode": modeString,
|
||||
@"dropboxToken": dropboxToken,
|
||||
@"shouldShare": @(shouldShare),
|
||||
@"rtmpStreamKey": rtmpStreamKey,
|
||||
@"rtmpBroadcastID": rtmpBroadcastID,
|
||||
@"youtubeStreamKey": youtubeStreamKey,
|
||||
@"youtubeBroadcastID": youtubeBroadcastID,
|
||||
@"extraMetadata": extraMetadata,
|
||||
@"transcription": @(transcription)
|
||||
};
|
||||
|
||||
[self sendEventWithName:startRecordingAction body:data];
|
||||
}
|
||||
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription {
|
||||
NSString *modeString = RecordingModeToString(mode);
|
||||
NSDictionary *data = @{
|
||||
@"mode": modeString,
|
||||
@"transcription": @(transcription)
|
||||
};
|
||||
|
||||
[self sendEventWithName:stopRecordingAction body:data];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
|
||||
#import <JitsiMeetSDK/JitsiMeetConferenceOptions.h>
|
||||
|
||||
// Matches RTCLoggingSeverity from RTCLogging.h
|
||||
typedef NS_ENUM(NSInteger, WebRTCLoggingSeverity) {
|
||||
WebRTCLoggingSeverityVerbose,
|
||||
WebRTCLoggingSeverityInfo,
|
||||
WebRTCLoggingSeverityWarning,
|
||||
WebRTCLoggingSeverityError,
|
||||
WebRTCLoggingSeverityNone,
|
||||
};
|
||||
|
||||
@interface JitsiMeet : NSObject
|
||||
|
||||
/**
|
||||
@@ -26,20 +35,35 @@
|
||||
* SiriKit or Handoff, for example.
|
||||
*/
|
||||
@property (copy, nonatomic, nullable) NSString *conferenceActivityType;
|
||||
|
||||
/**
|
||||
* Custom URL scheme used for deep-linking.
|
||||
*/
|
||||
@property (copy, nonatomic, nullable) NSString *customUrlScheme;
|
||||
|
||||
/**
|
||||
* List of domains used for universal linking.
|
||||
*/
|
||||
@property (copy, nonatomic, nullable) NSArray<NSString *> *universalLinkDomains;
|
||||
|
||||
/**
|
||||
* Default conference options used for all conferences. These options will be merged
|
||||
* with those passed to JitsiMeetView.join when joining a conference.
|
||||
*/
|
||||
@property (nonatomic, nullable) JitsiMeetConferenceOptions *defaultConferenceOptions;
|
||||
|
||||
/**
|
||||
* Custom RTCAudioDevice implementation.
|
||||
* https://github.com/jitsi/webrtc/blob/M124/sdk/objc/components/audio/RTCAudioDevice.h
|
||||
* https://github.com/mstyura/RTCAudioDevice
|
||||
*/
|
||||
@property (nonatomic, strong, nullable) id rtcAudioDevice;
|
||||
|
||||
/**
|
||||
* Specify WebRTC logging severity.
|
||||
*/
|
||||
@property (nonatomic, assign) WebRTCLoggingSeverity webRtcLoggingSeverity;
|
||||
|
||||
#pragma mark - This class is a singleton
|
||||
|
||||
+ (instancetype _Nonnull)sharedInstance;
|
||||
|
||||
@@ -54,14 +54,9 @@
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
#if 0
|
||||
// Initialize WebRTC options.
|
||||
WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
|
||||
options.loggingSeverity = RTCLoggingSeverityInfo;
|
||||
#endif
|
||||
|
||||
// Initialize the one and only bridge for interfacing with React Native.
|
||||
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
|
||||
self.rtcAudioDevice = nil;
|
||||
self.webRtcLoggingSeverity = WebRTCLoggingSeverityNone;
|
||||
|
||||
// Initialize the listener for handling start/stop screensharing notifications.
|
||||
_screenshareEventEmiter = [[ScheenshareEventEmiter alloc] init];
|
||||
@@ -142,6 +137,12 @@
|
||||
return;
|
||||
};
|
||||
|
||||
// Initialize WebRTC options.
|
||||
WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
|
||||
options.audioDevice = _rtcAudioDevice;
|
||||
options.loggingSeverity = (RTCLoggingSeverity)_webRtcLoggingSeverity;
|
||||
|
||||
// Initialize the one and only bridge for interfacing with React Native.
|
||||
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
|
||||
}
|
||||
|
||||
@@ -249,6 +250,8 @@
|
||||
}
|
||||
|
||||
- (RCTBridge *)getReactBridge {
|
||||
// Initialize bridge lazily.
|
||||
[self instantiateReactNativeBridge];
|
||||
return _bridgeWrapper.bridge;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#import "JitsiMeetConferenceOptions.h"
|
||||
#import "JitsiMeetViewDelegate.h"
|
||||
|
||||
typedef NS_ENUM(NSInteger, RecordingMode);
|
||||
|
||||
@interface JitsiMeetView : UIView
|
||||
|
||||
@property (nonatomic, nullable, weak) id<JitsiMeetViewDelegate> delegate;
|
||||
@@ -47,5 +49,9 @@
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
- (void)setClosedCaptionsEnabled:(BOOL)enabled;
|
||||
- (void)toggleCamera;
|
||||
- (void)showNotification:(NSString * _Nonnull)appearance :(NSString * _Nullable)description :(NSString * _Nullable)timeout :(NSString * _Nullable)title :(NSString * _Nullable)uid;
|
||||
- (void)hideNotification:(NSString * _Nullable)uid;
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString * _Nullable)dropboxToken :(BOOL)shouldShare :(NSString * _Nullable)rtmpStreamKey :(NSString * _Nullable)rtmpBroadcastID :(NSString * _Nullable)youtubeStreamKey :(NSString * _Nullable)youtubeBroadcastID :(NSString * _Nullable)extraMetadata :(BOOL)transcription;
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription;
|
||||
|
||||
@end
|
||||
|
||||
@@ -143,6 +143,26 @@ static NSString *const PiPEnabledFeatureFlag = @"pip.enabled";
|
||||
[externalAPI toggleCamera];
|
||||
}
|
||||
|
||||
- (void)showNotification:(NSString *)appearance :(NSString *)description :(NSString *)timeout :(NSString *)title :(NSString *)uid {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI showNotification:appearance :description :timeout :title :uid];
|
||||
}
|
||||
|
||||
-(void)hideNotification:(NSString *)uid {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI hideNotification:uid];
|
||||
}
|
||||
|
||||
- (void)startRecording:(RecordingMode)mode :(NSString *)dropboxToken :(BOOL)shouldShare :(NSString *)rtmpStreamKey :(NSString *)rtmpBroadcastID :(NSString *)youtubeStreamKey :(NSString *)youtubeBroadcastID :(NSString *)extraMetadata :(BOOL)transcription {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI startRecording:mode :dropboxToken :shouldShare :rtmpStreamKey :rtmpBroadcastID :youtubeStreamKey :youtubeBroadcastID :extraMetadata :transcription];
|
||||
}
|
||||
|
||||
- (void)stopRecording:(RecordingMode)mode :(BOOL)transcription {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI stopRecording:mode :transcription];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
- (void)registerObservers {
|
||||
|
||||
@@ -1405,7 +1405,8 @@
|
||||
"ccButtonTooltip": "Start / Stop subtitles",
|
||||
"expandedLabel": "Transcribing is currently on",
|
||||
"failed": "Transcribing failed",
|
||||
"labelToolTip": "The meeting is being transcribed",
|
||||
"labelTooltip": "This meeting is being transcribed.",
|
||||
"labelTooltipExtra": "In addition, a transcript will be available later.",
|
||||
"sourceLanguageDesc": "Currently the meeting language is set to <b>{{sourceLanguage}}</b>. <br/> You can change it from ",
|
||||
"sourceLanguageHere": "here",
|
||||
"start": "Start showing subtitles",
|
||||
@@ -1444,7 +1445,7 @@
|
||||
"ldTooltip": "Viewing low definition video",
|
||||
"lowDefinition": "Low definition",
|
||||
"performanceSettings": "Performance settings",
|
||||
"recording": "Recording in progress",
|
||||
"recording": "This meeting is being recorded.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Viewing standard definition video",
|
||||
"standardDefinition": "Standard definition",
|
||||
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
toggleChat
|
||||
} from '../../react/features/chat/actions';
|
||||
import { openChat } from '../../react/features/chat/actions.web';
|
||||
import { showDesktopPicker } from '../../react/features/desktop-picker/actions';
|
||||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
@@ -1048,6 +1049,23 @@ function initCommands() {
|
||||
}
|
||||
case '_new_electron_screensharing_supported': {
|
||||
callback(true);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'open-desktop-picker': {
|
||||
const { desktopSharingSources } = APP.store.getState()['features/base/config'];
|
||||
const options = {
|
||||
desktopSharingSources: desktopSharingSources ?? [ 'screen', 'window' ]
|
||||
};
|
||||
const onSourceChoose = (_streamId, _type, screenShareAudio, source) => {
|
||||
callback({
|
||||
screenShareAudio,
|
||||
source
|
||||
});
|
||||
};
|
||||
|
||||
dispatch(showDesktopPicker(options, onSourceChoose));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
13
modules/API/external/external_api.js
vendored
13
modules/API/external/external_api.js
vendored
@@ -1243,7 +1243,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
* @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
|
||||
* after with the legacy SS support was removed from the electron SDK. If we remove it now the SS for Electron
|
||||
* clients with older versions wont work.
|
||||
*/
|
||||
_isNewElectronScreensharingSupported() {
|
||||
@@ -1455,4 +1455,15 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
setVirtualBackground(enabled, backgroundImage) {
|
||||
this.executeCommand('setVirtualBackground', enabled, backgroundImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the desktop picker. This is invoked by the Electron SDK when gDM is used.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_openDesktopPicker() {
|
||||
return this._transport.sendRequest({
|
||||
name: 'open-desktop-picker'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export default from './API';
|
||||
export { default } from './API';
|
||||
export * from './constants';
|
||||
|
||||
416
package-lock.json
generated
416
package-lock.json
generated
@@ -20,7 +20,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.2.0",
|
||||
"@jitsi/rnnoise-wasm": "0.2.1",
|
||||
"@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",
|
||||
@@ -62,14 +62,13 @@
|
||||
"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/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1912.0.0+522577a4/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"null-loader": "4.0.1",
|
||||
"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",
|
||||
@@ -78,7 +77,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.75.4",
|
||||
"react-native": "0.75.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
@@ -125,12 +124,11 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.25.9",
|
||||
"@babel/eslint-parser": "7.25.9",
|
||||
"@babel/plugin-proposal-export-default-from": "7.25.9",
|
||||
"@babel/plugin-transform-private-methods": "7.25.9",
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "5.0.9",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-native/metro-config": "0.75.5",
|
||||
"@stylistic/eslint-plugin": "2.12.1",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
@@ -169,6 +167,7 @@
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
"clean-css-cli": "4.3.0",
|
||||
"core-js": "3.40.0",
|
||||
"css-loader": "6.8.1",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
@@ -4199,9 +4198,9 @@
|
||||
"integrity": "sha512-L/gnRjnFxpQqvfILXeFoVTHEtwL01zoSKAIqMVxMmaD0ahozML8uztlxRPlYSbKrT31j3Cm1ss9KdSNeO3f9FQ=="
|
||||
},
|
||||
"node_modules/@jitsi/rnnoise-wasm": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rnnoise-wasm/-/rnnoise-wasm-0.2.0.tgz",
|
||||
"integrity": "sha512-ZpNws6UjbNjkhD68DQu862IjzfjYgLOwknqdlLotPbJqnBTA91vo5uh9+D0aUqSoY/U1aQhPZZB3IRj5ZNfw3Q=="
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rnnoise-wasm/-/rnnoise-wasm-0.2.1.tgz",
|
||||
"integrity": "sha512-iEj77www43pS2Yq+cfLZb+hFuI7L5ccisBzzPMcOjjLsG4/LAlkD1CY58/8gc84nHdLBGmD/OPIWGnvYnXvB0A=="
|
||||
},
|
||||
"node_modules/@jitsi/rtcstats": {
|
||||
"version": "9.5.1",
|
||||
@@ -5960,30 +5959,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/assets-registry": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.4.tgz",
|
||||
"integrity": "sha512-WX6/LNHwyjislSFM+h3qQjBiPaXXPJW5ZV4TdgNKb6QOPO0g1KGYRQj44cI2xSpZ3fcWrvQFZfQgSMbVK9Sg7A==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.5.tgz",
|
||||
"integrity": "sha512-5Arp90qqwT4svF9izcFxM2tiDZlHSVKBuLT6gS1FbOABzHkKDK06Pv+CWNKZ2TM0l7qxRxNAd6e86tvnkMlGZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/babel-plugin-codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-gu5ZRIdr7+ufi09DJROhfDtbF4biTnCDJqtqcmtsku4cXOXPHE36QbC/vAmKEZ0PMPURBI8lwF2wfaeHLn7gig==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-WUYzABcKhjCYCFsMTkvJ4CPSSeQ0ayLoIQjN87NtgrKzRq6yISTxF2J/2vgUuJgT99U7J6x6AgdNqlQ/U6M+qw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native/codegen": "0.75.4"
|
||||
"@react-native/codegen": "0.75.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/babel-preset": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.4.tgz",
|
||||
"integrity": "sha512-UtyYCDJ3rZIeggyFEfh/q5t/FZ5a1h9F8EI37Nbrwyk/OKPH+1XS4PbHROHJzBARlJwOAfmT75+ovYUO0eakJA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.5.tgz",
|
||||
"integrity": "sha512-VEJvWUu2zg9PacO6RL3n10TQ05IdbTIKiCpRAulexSAZZamURelX8Ko9VbWpIP0Wg/zFyYoDwsKOhCe+rFfhvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
@@ -6028,7 +6027,7 @@
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"@react-native/babel-plugin-codegen": "0.75.4",
|
||||
"@react-native/babel-plugin-codegen": "0.75.5",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"react-refresh": "^0.14.0"
|
||||
},
|
||||
@@ -6049,9 +6048,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-0FplNAD/S5FUvm8YIn6uyarOcP4jdJPqWz17K4a/Gp2KSsG/JJKEskX3aj5wpePzVfNQl3WyvBJ0whODdCocIA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-xIhr7UpnUySYJGgQmLrTPtTcaDlri7YECkAJwcLAxdDu2KVJzGoxNdHa2poCkTLceDBxvL+3hYYsoevl1PD45w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.20.0",
|
||||
@@ -6086,15 +6085,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/community-cli-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-k/hevYPjEpW0MNVVyb3v9PJosOP+FzenS7+oqYNLXdEmgTnGHrAtYX9ABrJJgzeJt7I6g8g+RDvm8PSE+tnM5w==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-rtZqsTb+2gtPw8BZgJlZ7p5ycXSGu4uql1gxybcv9qNgqlsNBZofiIdZEAade67/GfeQEvy/UY96SvoIsmVW6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native-community/cli-server-api": "14.1.0",
|
||||
"@react-native-community/cli-tools": "14.1.0",
|
||||
"@react-native/dev-middleware": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/dev-middleware": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"chalk": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"metro": "^0.80.3",
|
||||
@@ -6178,22 +6177,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/debugger-frontend": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.4.tgz",
|
||||
"integrity": "sha512-QfGurR5hV6bhMPn/6VxS2RomYrPRFGwA03jJr+zKyWHnxDAu5jOqYVyKAktIIbhYe5sPp78QVl1ZYuhcnsRbEw==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.5.tgz",
|
||||
"integrity": "sha512-/mW0ELovNVAdZZFs0JC9147zF/GlvgrFPHhLhkgT95lOir/8cACLvR1kdVnH59sAQw2zKxyrFbrwhO80zPfp9g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/dev-middleware": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.4.tgz",
|
||||
"integrity": "sha512-UhyBeQOG2wNcvrUGw3+IBrHBk/lIu7hHGmWt4j8W9Aqv9BwktHKkPyko+5A1yoUeO1O/VDnHWYqWeOejcA9wpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.5.tgz",
|
||||
"integrity": "sha512-agVYocKP6YjW2g296jS1fipvC4U1vaykPnIgIgQfXhhVAYsFFJwO4Fh8oLcI82ceGc3mikv9cPrv8IrUoIWKsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@react-native/debugger-frontend": "0.75.4",
|
||||
"@react-native/debugger-frontend": "0.75.5",
|
||||
"chrome-launcher": "^0.15.2",
|
||||
"chromium-edge-launcher": "^0.2.0",
|
||||
"connect": "^3.6.5",
|
||||
@@ -6225,31 +6224,31 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native/gradle-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-kKTmw7cF7p1raT30DC0L6N+xiVXN7dlRy0J+hYPiCRRVHplwgvyS7pszjxfzwXmHFqOxwpxQVI3du8opsma1Mg==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-s6lPOVPLoGdQNbfgkVHvCC5Mg/mdTkg+5l5gChG6tNI4LZ6NVvaN0Ettt8bIKMoYlExsXQ2zpdBLopnjro5JgA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/js-polyfills": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.4.tgz",
|
||||
"integrity": "sha512-NF5ID5FjcVHBYk1LQ4JMRjPmxBWEo4yoqW1m6vGOQZPT8D5Qs9afgx3f7gQatxbn3ivMh0FVbLW0zBx6LyxEzA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.5.tgz",
|
||||
"integrity": "sha512-zDMMq2mtWZ1/P3CnOguBucMSsvzPSDuLHZPn33DjZv1VFSOjqyAbGy9F7ZEQ0Y+vNiApOH9tPcsBZRFuTATzng==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/metro-babel-transformer": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.4.tgz",
|
||||
"integrity": "sha512-O0WMW/K8Ny/MAAeRebqGEQhrbzcioxcPHZtos+EH2hWeBTEKHQV8fMYYxfYDabpr392qdhSBwg3LlXUD4U3PXQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.5.tgz",
|
||||
"integrity": "sha512-As/3zryghF12L983GaoBIgJFsDsepZfMCGGYz3zsfRqh4JjYaaGR5QAoWkk+NE+VDOwnvDp3zoVYtAqsu3wPbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@react-native/babel-preset": "0.75.4",
|
||||
"@react-native/babel-preset": "0.75.5",
|
||||
"hermes-parser": "0.22.0",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
@@ -6276,14 +6275,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/metro-config": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.4.tgz",
|
||||
"integrity": "sha512-gIIVlPUtZ1UKCxMJRtG88FoWS5REbI4YUmiyoM8eBUA85Zvk27b67iBX5Lkuxg8FGc7t9tjWQRpVGs2IK5uZpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.5.tgz",
|
||||
"integrity": "sha512-OrWZtYEznRRoTa4Cjj7zN2F6fZqfgzased7UQt7qRx1/O8DTrdVJDin4FBJ0mD570jTFF4yLHXcpgTbthPlBbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"metro-config": "^0.80.3",
|
||||
"metro-runtime": "^0.80.3"
|
||||
},
|
||||
@@ -6292,15 +6291,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/normalize-colors": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.4.tgz",
|
||||
"integrity": "sha512-90QrQDLg0/k9xqYesaKuIkayOSjD+FKa0hsHollbwT5h3kuGMY+lU7UZxnb8tU55Y1PKdvjYxqQsYWI/ql79zA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.5.tgz",
|
||||
"integrity": "sha512-8yooxuLIwxI11O+pJ9LDtiaAiyXQeubROXALdAhU3kZQI49Nz0WB1ugLWj0Et+D8miD+79XPOFflcUqqoIY1nw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@react-native/virtualized-lists": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.4.tgz",
|
||||
"integrity": "sha512-iEauRiXjvWG/iOH8bV+9MfepCS+72cuL5rhkrenYZS0NUnDcNjF+wtaoS9+Gx5z1UJOfEXxSmyXRtQJZne8SnA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.5.tgz",
|
||||
"integrity": "sha512-B1XMDXgptPpeWmmZABverRE9tBjDYuP97wQ/FeU7gHhZ+cwD+w68mwFZxcivr5fDz0Z4FaGlM7pR6oGjEn8NXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"invariant": "^2.2.4",
|
||||
@@ -9529,24 +9528,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.map": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.4.tgz",
|
||||
"integrity": "sha512-Qds9QnX7A0qISY7JT5WuJO0NJPE9CMlC6JzHQfhpqAAQQzufVRoeH7EzUY5GcPTx72voG8LV/5eo+b8Qi8hmhA==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.19.0",
|
||||
"es-array-method-boxes-properly": "^1.0.0",
|
||||
"is-string": "^1.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/array.prototype.tosorted": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
|
||||
@@ -11092,6 +11073,18 @@
|
||||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.40.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
|
||||
"integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js-compat": {
|
||||
"version": "3.38.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
|
||||
@@ -12244,11 +12237,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-array-method-boxes-properly": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
|
||||
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
@@ -12265,24 +12253,6 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-get-iterator": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz",
|
||||
"integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.1.0",
|
||||
"has-symbols": "^1.0.1",
|
||||
"is-arguments": "^1.1.0",
|
||||
"is-map": "^2.0.2",
|
||||
"is-set": "^2.0.2",
|
||||
"is-string": "^1.0.5",
|
||||
"isarray": "^2.0.5"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-iterator-helpers": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
|
||||
@@ -15721,26 +15691,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iterate-iterator": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz",
|
||||
"integrity": "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/iterate-value": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz",
|
||||
"integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==",
|
||||
"dependencies": {
|
||||
"es-get-iterator": "^1.0.2",
|
||||
"iterate-iterator": "^1.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/iterator.prototype": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
|
||||
@@ -16959,8 +16909,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-POz5n9QppExZNX4ZguKoQOOLL0Gev2RL49ZtwiWs7QBHTat4gzBA9kYAeviRrmW4kVn7xcX0xeAxInbHDWs4lg==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1912.0.0+522577a4/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-9gWT8koE7bS/32LuYrUKdsFYjJ0mkyQ1ctANG0KlRnEDqIzx4T+C+6F+RltiytSNxsMC+08+h1uC4BSxgqzyng==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -20085,25 +20035,6 @@
|
||||
"asap": "~2.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/promise.allsettled": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.4.tgz",
|
||||
"integrity": "sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag==",
|
||||
"dependencies": {
|
||||
"array.prototype.map": "^1.0.3",
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.18.0-next.2",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"iterate-value": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/promise.withresolvers": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/promise.withresolvers/-/promise.withresolvers-1.0.3.tgz",
|
||||
@@ -20526,22 +20457,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz",
|
||||
"integrity": "sha512-Jehg4AMNIAXu9cn0/1jbTCoNg3tc+t6EekwucCalN8YoRmxGd/PY6osQTI/5fSAM40JQ4O8uv8Qg09Ycpb5sxQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.5.tgz",
|
||||
"integrity": "sha512-9vyUHhkmesL2A++b3dKuhprEq7Nh/1gKgB7MD0ybjCgI4ARob0+j6Myfphzes8fYRcVSAwuiNdp8H+N96+eXSA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@react-native-community/cli": "14.1.0",
|
||||
"@react-native-community/cli-platform-android": "14.1.0",
|
||||
"@react-native-community/cli-platform-ios": "14.1.0",
|
||||
"@react-native/assets-registry": "0.75.4",
|
||||
"@react-native/codegen": "0.75.4",
|
||||
"@react-native/community-cli-plugin": "0.75.4",
|
||||
"@react-native/gradle-plugin": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/normalize-colors": "0.75.4",
|
||||
"@react-native/virtualized-lists": "0.75.4",
|
||||
"@react-native/assets-registry": "0.75.5",
|
||||
"@react-native/codegen": "0.75.5",
|
||||
"@react-native/community-cli-plugin": "0.75.5",
|
||||
"@react-native/gradle-plugin": "0.75.5",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/normalize-colors": "0.75.5",
|
||||
"@react-native/virtualized-lists": "0.75.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"anser": "^1.4.9",
|
||||
"ansi-regex": "^5.0.0",
|
||||
@@ -28697,9 +28628,9 @@
|
||||
"integrity": "sha512-L/gnRjnFxpQqvfILXeFoVTHEtwL01zoSKAIqMVxMmaD0ahozML8uztlxRPlYSbKrT31j3Cm1ss9KdSNeO3f9FQ=="
|
||||
},
|
||||
"@jitsi/rnnoise-wasm": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rnnoise-wasm/-/rnnoise-wasm-0.2.0.tgz",
|
||||
"integrity": "sha512-ZpNws6UjbNjkhD68DQu862IjzfjYgLOwknqdlLotPbJqnBTA91vo5uh9+D0aUqSoY/U1aQhPZZB3IRj5ZNfw3Q=="
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/rnnoise-wasm/-/rnnoise-wasm-0.2.1.tgz",
|
||||
"integrity": "sha512-iEj77www43pS2Yq+cfLZb+hFuI7L5ccisBzzPMcOjjLsG4/LAlkD1CY58/8gc84nHdLBGmD/OPIWGnvYnXvB0A=="
|
||||
},
|
||||
"@jitsi/rtcstats": {
|
||||
"version": "9.5.1",
|
||||
@@ -29811,22 +29742,22 @@
|
||||
"integrity": "sha512-9hhIlnNMfB6NO2738M437egA1p6inKdXXazh0qf6HJeBuLBTePSecePCOkVPczivM1mglquYXWCHJlx4D3Z7xw=="
|
||||
},
|
||||
"@react-native/assets-registry": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.4.tgz",
|
||||
"integrity": "sha512-WX6/LNHwyjislSFM+h3qQjBiPaXXPJW5ZV4TdgNKb6QOPO0g1KGYRQj44cI2xSpZ3fcWrvQFZfQgSMbVK9Sg7A=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.5.tgz",
|
||||
"integrity": "sha512-5Arp90qqwT4svF9izcFxM2tiDZlHSVKBuLT6gS1FbOABzHkKDK06Pv+CWNKZ2TM0l7qxRxNAd6e86tvnkMlGZw=="
|
||||
},
|
||||
"@react-native/babel-plugin-codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-gu5ZRIdr7+ufi09DJROhfDtbF4biTnCDJqtqcmtsku4cXOXPHE36QbC/vAmKEZ0PMPURBI8lwF2wfaeHLn7gig==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-WUYzABcKhjCYCFsMTkvJ4CPSSeQ0ayLoIQjN87NtgrKzRq6yISTxF2J/2vgUuJgT99U7J6x6AgdNqlQ/U6M+qw==",
|
||||
"requires": {
|
||||
"@react-native/codegen": "0.75.4"
|
||||
"@react-native/codegen": "0.75.5"
|
||||
}
|
||||
},
|
||||
"@react-native/babel-preset": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.4.tgz",
|
||||
"integrity": "sha512-UtyYCDJ3rZIeggyFEfh/q5t/FZ5a1h9F8EI37Nbrwyk/OKPH+1XS4PbHROHJzBARlJwOAfmT75+ovYUO0eakJA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.5.tgz",
|
||||
"integrity": "sha512-VEJvWUu2zg9PacO6RL3n10TQ05IdbTIKiCpRAulexSAZZamURelX8Ko9VbWpIP0Wg/zFyYoDwsKOhCe+rFfhvA==",
|
||||
"requires": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
@@ -29870,7 +29801,7 @@
|
||||
"@babel/plugin-transform-typescript": "^7.5.0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0",
|
||||
"@babel/template": "^7.0.0",
|
||||
"@react-native/babel-plugin-codegen": "0.75.4",
|
||||
"@react-native/babel-plugin-codegen": "0.75.5",
|
||||
"babel-plugin-transform-flow-enums": "^0.0.2",
|
||||
"react-refresh": "^0.14.0"
|
||||
},
|
||||
@@ -29883,9 +29814,9 @@
|
||||
}
|
||||
},
|
||||
"@react-native/codegen": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.4.tgz",
|
||||
"integrity": "sha512-0FplNAD/S5FUvm8YIn6uyarOcP4jdJPqWz17K4a/Gp2KSsG/JJKEskX3aj5wpePzVfNQl3WyvBJ0whODdCocIA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.5.tgz",
|
||||
"integrity": "sha512-xIhr7UpnUySYJGgQmLrTPtTcaDlri7YECkAJwcLAxdDu2KVJzGoxNdHa2poCkTLceDBxvL+3hYYsoevl1PD45w==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.20.0",
|
||||
"glob": "^7.1.1",
|
||||
@@ -29913,14 +29844,14 @@
|
||||
}
|
||||
},
|
||||
"@react-native/community-cli-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-k/hevYPjEpW0MNVVyb3v9PJosOP+FzenS7+oqYNLXdEmgTnGHrAtYX9ABrJJgzeJt7I6g8g+RDvm8PSE+tnM5w==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-rtZqsTb+2gtPw8BZgJlZ7p5ycXSGu4uql1gxybcv9qNgqlsNBZofiIdZEAade67/GfeQEvy/UY96SvoIsmVW6A==",
|
||||
"requires": {
|
||||
"@react-native-community/cli-server-api": "14.1.0",
|
||||
"@react-native-community/cli-tools": "14.1.0",
|
||||
"@react-native/dev-middleware": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/dev-middleware": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"chalk": "^4.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"metro": "^0.80.3",
|
||||
@@ -29976,17 +29907,17 @@
|
||||
}
|
||||
},
|
||||
"@react-native/debugger-frontend": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.4.tgz",
|
||||
"integrity": "sha512-QfGurR5hV6bhMPn/6VxS2RomYrPRFGwA03jJr+zKyWHnxDAu5jOqYVyKAktIIbhYe5sPp78QVl1ZYuhcnsRbEw=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.5.tgz",
|
||||
"integrity": "sha512-/mW0ELovNVAdZZFs0JC9147zF/GlvgrFPHhLhkgT95lOir/8cACLvR1kdVnH59sAQw2zKxyrFbrwhO80zPfp9g=="
|
||||
},
|
||||
"@react-native/dev-middleware": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.4.tgz",
|
||||
"integrity": "sha512-UhyBeQOG2wNcvrUGw3+IBrHBk/lIu7hHGmWt4j8W9Aqv9BwktHKkPyko+5A1yoUeO1O/VDnHWYqWeOejcA9wpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.5.tgz",
|
||||
"integrity": "sha512-agVYocKP6YjW2g296jS1fipvC4U1vaykPnIgIgQfXhhVAYsFFJwO4Fh8oLcI82ceGc3mikv9cPrv8IrUoIWKsg==",
|
||||
"requires": {
|
||||
"@isaacs/ttlcache": "^1.4.1",
|
||||
"@react-native/debugger-frontend": "0.75.4",
|
||||
"@react-native/debugger-frontend": "0.75.5",
|
||||
"chrome-launcher": "^0.15.2",
|
||||
"chromium-edge-launcher": "^0.2.0",
|
||||
"connect": "^3.6.5",
|
||||
@@ -30015,22 +29946,22 @@
|
||||
}
|
||||
},
|
||||
"@react-native/gradle-plugin": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.4.tgz",
|
||||
"integrity": "sha512-kKTmw7cF7p1raT30DC0L6N+xiVXN7dlRy0J+hYPiCRRVHplwgvyS7pszjxfzwXmHFqOxwpxQVI3du8opsma1Mg=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.5.tgz",
|
||||
"integrity": "sha512-s6lPOVPLoGdQNbfgkVHvCC5Mg/mdTkg+5l5gChG6tNI4LZ6NVvaN0Ettt8bIKMoYlExsXQ2zpdBLopnjro5JgA=="
|
||||
},
|
||||
"@react-native/js-polyfills": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.4.tgz",
|
||||
"integrity": "sha512-NF5ID5FjcVHBYk1LQ4JMRjPmxBWEo4yoqW1m6vGOQZPT8D5Qs9afgx3f7gQatxbn3ivMh0FVbLW0zBx6LyxEzA=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.5.tgz",
|
||||
"integrity": "sha512-zDMMq2mtWZ1/P3CnOguBucMSsvzPSDuLHZPn33DjZv1VFSOjqyAbGy9F7ZEQ0Y+vNiApOH9tPcsBZRFuTATzng=="
|
||||
},
|
||||
"@react-native/metro-babel-transformer": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.4.tgz",
|
||||
"integrity": "sha512-O0WMW/K8Ny/MAAeRebqGEQhrbzcioxcPHZtos+EH2hWeBTEKHQV8fMYYxfYDabpr392qdhSBwg3LlXUD4U3PXQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.5.tgz",
|
||||
"integrity": "sha512-As/3zryghF12L983GaoBIgJFsDsepZfMCGGYz3zsfRqh4JjYaaGR5QAoWkk+NE+VDOwnvDp3zoVYtAqsu3wPbQ==",
|
||||
"requires": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@react-native/babel-preset": "0.75.4",
|
||||
"@react-native/babel-preset": "0.75.5",
|
||||
"hermes-parser": "0.22.0",
|
||||
"nullthrows": "^1.1.1"
|
||||
},
|
||||
@@ -30051,26 +29982,26 @@
|
||||
}
|
||||
},
|
||||
"@react-native/metro-config": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.4.tgz",
|
||||
"integrity": "sha512-gIIVlPUtZ1UKCxMJRtG88FoWS5REbI4YUmiyoM8eBUA85Zvk27b67iBX5Lkuxg8FGc7t9tjWQRpVGs2IK5uZpQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.75.5.tgz",
|
||||
"integrity": "sha512-OrWZtYEznRRoTa4Cjj7zN2F6fZqfgzased7UQt7qRx1/O8DTrdVJDin4FBJ0mD570jTFF4yLHXcpgTbthPlBbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/metro-babel-transformer": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/metro-babel-transformer": "0.75.5",
|
||||
"metro-config": "^0.80.3",
|
||||
"metro-runtime": "^0.80.3"
|
||||
}
|
||||
},
|
||||
"@react-native/normalize-colors": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.4.tgz",
|
||||
"integrity": "sha512-90QrQDLg0/k9xqYesaKuIkayOSjD+FKa0hsHollbwT5h3kuGMY+lU7UZxnb8tU55Y1PKdvjYxqQsYWI/ql79zA=="
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.5.tgz",
|
||||
"integrity": "sha512-8yooxuLIwxI11O+pJ9LDtiaAiyXQeubROXALdAhU3kZQI49Nz0WB1ugLWj0Et+D8miD+79XPOFflcUqqoIY1nw=="
|
||||
},
|
||||
"@react-native/virtualized-lists": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.4.tgz",
|
||||
"integrity": "sha512-iEauRiXjvWG/iOH8bV+9MfepCS+72cuL5rhkrenYZS0NUnDcNjF+wtaoS9+Gx5z1UJOfEXxSmyXRtQJZne8SnA==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.5.tgz",
|
||||
"integrity": "sha512-B1XMDXgptPpeWmmZABverRE9tBjDYuP97wQ/FeU7gHhZ+cwD+w68mwFZxcivr5fDz0Z4FaGlM7pR6oGjEn8NXA==",
|
||||
"requires": {
|
||||
"invariant": "^2.2.4",
|
||||
"nullthrows": "^1.1.1"
|
||||
@@ -32427,18 +32358,6 @@
|
||||
"es-shim-unscopables": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"array.prototype.map": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.4.tgz",
|
||||
"integrity": "sha512-Qds9QnX7A0qISY7JT5WuJO0NJPE9CMlC6JzHQfhpqAAQQzufVRoeH7EzUY5GcPTx72voG8LV/5eo+b8Qi8hmhA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.19.0",
|
||||
"es-array-method-boxes-properly": "^1.0.0",
|
||||
"is-string": "^1.0.7"
|
||||
}
|
||||
},
|
||||
"array.prototype.tosorted": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
|
||||
@@ -33572,6 +33491,12 @@
|
||||
"toggle-selection": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"core-js": {
|
||||
"version": "3.40.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
|
||||
"integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
|
||||
"dev": true
|
||||
},
|
||||
"core-js-compat": {
|
||||
"version": "3.38.1",
|
||||
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz",
|
||||
@@ -34398,11 +34323,6 @@
|
||||
"which-typed-array": "^1.1.18"
|
||||
}
|
||||
},
|
||||
"es-array-method-boxes-properly": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
|
||||
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
@@ -34413,21 +34333,6 @@
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"es-get-iterator": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz",
|
||||
"integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.1.0",
|
||||
"has-symbols": "^1.0.1",
|
||||
"is-arguments": "^1.1.0",
|
||||
"is-map": "^2.0.2",
|
||||
"is-set": "^2.0.2",
|
||||
"is-string": "^1.0.5",
|
||||
"isarray": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"es-iterator-helpers": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz",
|
||||
@@ -36819,20 +36724,6 @@
|
||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
|
||||
},
|
||||
"iterate-iterator": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz",
|
||||
"integrity": "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="
|
||||
},
|
||||
"iterate-value": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz",
|
||||
"integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==",
|
||||
"requires": {
|
||||
"es-get-iterator": "^1.0.2",
|
||||
"iterate-iterator": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"iterator.prototype": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
|
||||
@@ -37746,8 +37637,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-POz5n9QppExZNX4ZguKoQOOLL0Gev2RL49ZtwiWs7QBHTat4gzBA9kYAeviRrmW4kVn7xcX0xeAxInbHDWs4lg==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1912.0.0+522577a4/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-9gWT8koE7bS/32LuYrUKdsFYjJ0mkyQ1ctANG0KlRnEDqIzx4T+C+6F+RltiytSNxsMC+08+h1uC4BSxgqzyng==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -39992,19 +39883,6 @@
|
||||
"asap": "~2.0.6"
|
||||
}
|
||||
},
|
||||
"promise.allsettled": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.4.tgz",
|
||||
"integrity": "sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag==",
|
||||
"requires": {
|
||||
"array.prototype.map": "^1.0.3",
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.18.0-next.2",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"iterate-value": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"promise.withresolvers": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/promise.withresolvers/-/promise.withresolvers-1.0.3.tgz",
|
||||
@@ -40304,21 +40182,21 @@
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.75.4",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.4.tgz",
|
||||
"integrity": "sha512-Jehg4AMNIAXu9cn0/1jbTCoNg3tc+t6EekwucCalN8YoRmxGd/PY6osQTI/5fSAM40JQ4O8uv8Qg09Ycpb5sxQ==",
|
||||
"version": "0.75.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.5.tgz",
|
||||
"integrity": "sha512-9vyUHhkmesL2A++b3dKuhprEq7Nh/1gKgB7MD0ybjCgI4ARob0+j6Myfphzes8fYRcVSAwuiNdp8H+N96+eXSA==",
|
||||
"requires": {
|
||||
"@jest/create-cache-key-function": "^29.6.3",
|
||||
"@react-native-community/cli": "14.1.0",
|
||||
"@react-native-community/cli-platform-android": "14.1.0",
|
||||
"@react-native-community/cli-platform-ios": "14.1.0",
|
||||
"@react-native/assets-registry": "0.75.4",
|
||||
"@react-native/codegen": "0.75.4",
|
||||
"@react-native/community-cli-plugin": "0.75.4",
|
||||
"@react-native/gradle-plugin": "0.75.4",
|
||||
"@react-native/js-polyfills": "0.75.4",
|
||||
"@react-native/normalize-colors": "0.75.4",
|
||||
"@react-native/virtualized-lists": "0.75.4",
|
||||
"@react-native/assets-registry": "0.75.5",
|
||||
"@react-native/codegen": "0.75.5",
|
||||
"@react-native/community-cli-plugin": "0.75.5",
|
||||
"@react-native/gradle-plugin": "0.75.5",
|
||||
"@react-native/js-polyfills": "0.75.5",
|
||||
"@react-native/normalize-colors": "0.75.5",
|
||||
"@react-native/virtualized-lists": "0.75.5",
|
||||
"abort-controller": "^3.0.0",
|
||||
"anser": "^1.4.9",
|
||||
"ansi-regex": "^5.0.0",
|
||||
|
||||
13
package.json
13
package.json
@@ -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.2.0",
|
||||
"@jitsi/rnnoise-wasm": "0.2.1",
|
||||
"@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,14 +68,13 @@
|
||||
"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/v1908.0.0+2a5d7fcc/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1912.0.0+522577a4/lib-jitsi-meet.tgz",
|
||||
"lodash-es": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"null-loader": "4.0.1",
|
||||
"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",
|
||||
@@ -84,7 +83,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.75.4",
|
||||
"react-native": "0.75.5",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
@@ -131,12 +130,11 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.25.9",
|
||||
"@babel/eslint-parser": "7.25.9",
|
||||
"@babel/plugin-proposal-export-default-from": "7.25.9",
|
||||
"@babel/plugin-transform-private-methods": "7.25.9",
|
||||
"@babel/preset-env": "7.25.9",
|
||||
"@babel/preset-react": "7.25.9",
|
||||
"@jitsi/eslint-config": "5.0.9",
|
||||
"@react-native/metro-config": "0.75.4",
|
||||
"@react-native/metro-config": "0.75.5",
|
||||
"@stylistic/eslint-plugin": "2.12.1",
|
||||
"@types/amplitude-js": "8.16.5",
|
||||
"@types/audioworklet": "0.0.29",
|
||||
@@ -175,6 +173,7 @@
|
||||
"babel-plugin-optional-require": "0.3.1",
|
||||
"circular-dependency-plugin": "5.2.0",
|
||||
"clean-css-cli": "4.3.0",
|
||||
"core-js": "3.40.0",
|
||||
"css-loader": "6.8.1",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
@@ -224,8 +223,10 @@
|
||||
"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-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.firefox.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-dev-single": "DOTENV_CONFIG_PATH=tests/.env wdio run tests/wdio.dev.conf.ts --spec",
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -87,7 +87,6 @@ export async function createHandlers({ getState }: IStore) {
|
||||
amplitudeIncludeUTM,
|
||||
blackListedEvents,
|
||||
scriptURLs,
|
||||
googleAnalyticsTrackingId,
|
||||
matomoEndpoint,
|
||||
matomoSiteID,
|
||||
whiteListedEvents
|
||||
@@ -98,7 +97,6 @@ export async function createHandlers({ getState }: IStore) {
|
||||
amplitudeIncludeUTM,
|
||||
blackListedEvents,
|
||||
envType: deploymentInfo?.envType || 'dev',
|
||||
googleAnalyticsTrackingId,
|
||||
matomoEndpoint,
|
||||
matomoSiteID,
|
||||
group,
|
||||
@@ -221,9 +219,6 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
overwritesPrejoinConfigICEUrl?: boolean;
|
||||
overwritesSalesforceUrl?: boolean;
|
||||
overwritesSupportUrl?: boolean;
|
||||
overwritesWatchRTCConfigParams?: boolean;
|
||||
overwritesWatchRTCProxyUrl?: boolean;
|
||||
overwritesWatchRTCWSUrl?: boolean;
|
||||
server?: string;
|
||||
tenant?: string;
|
||||
wasLobbyVisible?: boolean;
|
||||
@@ -274,14 +269,6 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
|
||||
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)
|
||||
|
||||
@@ -14,7 +14,6 @@ interface IOptions {
|
||||
amplitudeIncludeUTM?: boolean;
|
||||
blackListedEvents?: string[];
|
||||
envType?: string;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
group?: string;
|
||||
host?: string;
|
||||
matomoEndpoint?: string;
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
/* global ga */
|
||||
|
||||
import { getJitsiMeetGlobalNS } from '../../base/util/helpers';
|
||||
|
||||
import AbstractHandler, { IEvent } from './AbstractHandler';
|
||||
|
||||
/**
|
||||
* Analytics handler for Google Analytics.
|
||||
*/
|
||||
class GoogleAnalyticsHandler extends AbstractHandler {
|
||||
_userProperties: Object;
|
||||
_userPropertiesString: string;
|
||||
|
||||
/**
|
||||
* Creates new instance of the GA analytics handler.
|
||||
*
|
||||
* @param {Object} options - The Google Analytics options.
|
||||
* @param {string} options.googleAnalyticsTrackingId - The GA track id
|
||||
* required by the GA API.
|
||||
*/
|
||||
constructor(options: any) {
|
||||
super(options);
|
||||
|
||||
this._userProperties = {};
|
||||
|
||||
if (!options.googleAnalyticsTrackingId) {
|
||||
throw new Error('Failed to initialize Google Analytics handler, no tracking ID');
|
||||
}
|
||||
|
||||
this._enabled = true;
|
||||
this._initGoogleAnalytics(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the ga object.
|
||||
*
|
||||
* @param {Object} options - The Google Analytics options.
|
||||
* @param {string} options.googleAnalyticsTrackingId - The GA track id
|
||||
* required by the GA API.
|
||||
* @returns {void}
|
||||
*/
|
||||
_initGoogleAnalytics(options: any) {
|
||||
/**
|
||||
* TODO: Keep this local, there's no need to add it to window.
|
||||
*/
|
||||
/* eslint-disable */ // @ts-ignore
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
// @ts-ignore
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
/* eslint-enable */
|
||||
// @ts-ignore
|
||||
ga('create', options.googleAnalyticsTrackingId, 'auto');
|
||||
|
||||
// @ts-ignore
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the integer to use for a Google Analytics event's value field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
*
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {number} - The integer to use for the 'value' of a Google
|
||||
* analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
|
||||
* suitable value.
|
||||
* @private
|
||||
*/
|
||||
_extractValue(event: IEvent) {
|
||||
let value: string | number | undefined = event?.attributes?.value;
|
||||
|
||||
// Try to extract an integer from the "value" attribute.
|
||||
value = Math.round(parseFloat(value ?? ''));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the string to use for a Google Analytics event's label field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
*
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {string} - The string to use for the 'label' of a Google
|
||||
* analytics event.
|
||||
* @private
|
||||
*/
|
||||
_extractLabel(event: IEvent) {
|
||||
const { attributes = {} } = event;
|
||||
const labelsArray
|
||||
= Object.keys(attributes).map(key => `${key}=${attributes[key]}`);
|
||||
|
||||
labelsArray.push(this._userPropertiesString);
|
||||
|
||||
return labelsArray.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the permanent properties for the current session.
|
||||
*
|
||||
* @param {Object} userProps - The permanent portperties.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUserProperties(userProps: any = {}) {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The label field is limited to 500B. We will concatenate all
|
||||
// attributes of the event, except the user agent because it may be
|
||||
// lengthy and is probably included from elsewhere.
|
||||
const filter = [ 'user_agent', 'callstats_name' ];
|
||||
|
||||
this._userPropertiesString
|
||||
= Object.keys(userProps)
|
||||
.filter(key => filter.indexOf(key) === -1)
|
||||
.map(key => `permanent_${key}=${userProps[key]}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the entry point of the API. The function sends an event to
|
||||
* google analytics. The format of the event is described in
|
||||
* analyticsAdapter in lib-jitsi-meet.
|
||||
*
|
||||
* @param {Object} event - The event in the format specified by
|
||||
* lib-jitsi-meet.
|
||||
* @returns {void}
|
||||
*/
|
||||
sendEvent(event: IEvent) {
|
||||
if (this._shouldIgnore(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gaEvent: {
|
||||
eventAction?: string;
|
||||
eventCategory: string;
|
||||
eventLabel: string;
|
||||
eventValue?: number;
|
||||
} = {
|
||||
'eventCategory': 'jitsi-meet',
|
||||
'eventAction': this._extractName(event),
|
||||
'eventLabel': this._extractLabel(event)
|
||||
};
|
||||
const value = this._extractValue(event);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
gaEvent.eventValue = value;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
ga('send', 'event', gaEvent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const globalNS = getJitsiMeetGlobalNS();
|
||||
|
||||
globalNS.analyticsHandlers = globalNS.analyticsHandlers || [];
|
||||
globalNS.analyticsHandlers.push(GoogleAnalyticsHandler);
|
||||
@@ -26,11 +26,6 @@ export interface IProps {
|
||||
* @abstract
|
||||
*/
|
||||
export class AbstractApp<P extends IProps = IProps> extends BaseApp<P> {
|
||||
/**
|
||||
* The deferred for the initialization {{promise, resolve, reject}}.
|
||||
*/
|
||||
_init: PromiseWithResolvers<any>;
|
||||
|
||||
/**
|
||||
* Initializes the app.
|
||||
*
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../mobile/react-native-sdk/middleware';
|
||||
import '../mobile/watchos/middleware';
|
||||
import '../share-room/middleware';
|
||||
import '../shared-video/middleware';
|
||||
import '../toolbox/middleware.native';
|
||||
import '../whiteboard/middleware.native';
|
||||
|
||||
import './middlewares.any';
|
||||
|
||||
@@ -237,7 +237,6 @@ export function getConferenceOptions(stateful: IStateful) {
|
||||
if (options.disableThirdPartyRequests) {
|
||||
delete config.analytics?.scriptURLs;
|
||||
delete config.analytics?.amplitudeAPPKey;
|
||||
delete config.analytics?.googleAnalyticsTrackingId;
|
||||
}
|
||||
|
||||
return options;
|
||||
|
||||
@@ -177,7 +177,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
dispatch(showErrorNotification({
|
||||
description: 'Restart initiated because of a bridge failure',
|
||||
titleKey: 'dialog.sessionRestarted'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -190,7 +190,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionArguments: { msg },
|
||||
descriptionKey: msg ? 'dialog.connectErrorWithMsg' : 'dialog.connectError',
|
||||
titleKey: 'connection.CONNFAIL'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -199,7 +199,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.maxUsersLimitReached',
|
||||
titleKey: 'dialog.maxUsersLimitReachedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
// In case of max users(it can be from a visitor node), let's restore
|
||||
// oldConfig if any as we will be back to the main prosody.
|
||||
@@ -236,7 +236,7 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionKey,
|
||||
hideErrorSupportLink: true,
|
||||
titleKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
}));
|
||||
|
||||
sendAnalytics(createNotAllowedErrorEvent(type, msg));
|
||||
|
||||
@@ -416,7 +416,7 @@ function _connectionFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
descriptionKey: errors ? 'dialog.tokenAuthFailedWithReasons' : 'dialog.tokenAuthFailed',
|
||||
descriptionArguments: { reason: errors },
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,6 @@ export interface IConfig {
|
||||
amplitudeIncludeUTM?: boolean;
|
||||
blackListedEvents?: string[];
|
||||
disabled?: boolean;
|
||||
googleAnalyticsTrackingId?: string;
|
||||
matomoEndpoint?: string;
|
||||
matomoSiteID?: string;
|
||||
obfuscateRoomName?: boolean;
|
||||
|
||||
@@ -20,7 +20,6 @@ export function _cleanupConfig(config: IConfig) {
|
||||
|
||||
if (NativeModules.AppInfo.LIBRE_BUILD) {
|
||||
delete config.analytics?.amplitudeAPPKey;
|
||||
delete config.analytics?.googleAnalyticsTrackingId;
|
||||
delete config.analytics?.rtcstatsEnabled;
|
||||
delete config.analytics?.rtcstatsEndpoint;
|
||||
delete config.analytics?.rtcstatsPollInterval;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable require-jsdoc */
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
@@ -157,7 +159,8 @@ export function getDevicesFromURL(state: IReduxState) {
|
||||
* @returns {Object} An object with the media devices split by type. The keys
|
||||
* are device type and the values are arrays with devices matching the device
|
||||
* type.
|
||||
*/
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['availableDevices'] {
|
||||
return {
|
||||
audioInput: devices.filter(device => device.kind === 'audioinput'),
|
||||
@@ -166,24 +169,16 @@ export function groupDevicesByKind(devices: MediaDeviceInfo[]): IDevicesState['a
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters audio devices from a list of MediaDeviceInfo objects.
|
||||
*
|
||||
* @param {Array<MediaDeviceInfo>} devices - Unfiltered media devices.
|
||||
* @private
|
||||
* @returns {Array<MediaDeviceInfo>} Filtered audio devices.
|
||||
*/
|
||||
export function filterAudioDevices(devices: MediaDeviceInfo[]) {
|
||||
return devices.filter(device => device.kind === 'audioinput');
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the devices that start with one of the prefixes from DEVICE_LABEL_PREFIXES_TO_IGNORE.
|
||||
*
|
||||
* @param {MediaDeviceInfo[]} devices - The devices to be filtered.
|
||||
* @returns {MediaDeviceInfo[]} - The filtered devices.
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) {
|
||||
|
||||
// @ts-ignore
|
||||
const ignoredDevices: MediaDeviceInfo[] = [];
|
||||
const filteredDevices = devices.filter(device => {
|
||||
if (!device.label) {
|
||||
@@ -212,6 +207,7 @@ export function filterIgnoredDevices(devices: MediaDeviceInfo[] = []) {
|
||||
* @param {MediaDeviceInfo[]} devices2 - Array with devices to be compared.
|
||||
* @returns {boolean} - True if the device arrays are different and false otherwise.
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function areDevicesDifferent(devices1: MediaDeviceInfo[] = [], devices2: MediaDeviceInfo[] = []) {
|
||||
if (devices1.length !== devices2.length) {
|
||||
return true;
|
||||
@@ -315,6 +311,7 @@ export function getVideoDeviceIds(state: IReduxState) {
|
||||
* @param {MediaDeviceInfo[]} devices - The devices.
|
||||
* @returns {string}
|
||||
*/
|
||||
// @ts-ignore
|
||||
function devicesToStr(devices?: MediaDeviceInfo[]) {
|
||||
return devices?.map(device => `\t\t${device.label}[${device.deviceId}]`).join('\n');
|
||||
}
|
||||
@@ -326,6 +323,7 @@ function devicesToStr(devices?: MediaDeviceInfo[]) {
|
||||
* @param {string} title - The title that will be printed in the log.
|
||||
* @returns {void}
|
||||
*/
|
||||
// @ts-ignore
|
||||
export function logDevices(devices: MediaDeviceInfo[], title = '') {
|
||||
const deviceList = groupDevicesByKind(devices);
|
||||
const audioInputs = devicesToStr(deviceList.audioInput);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import DeviceStatus from '../../../../prejoin/components/web/preview/DeviceStatus';
|
||||
import { isRoomNameEnabled } from '../../../../prejoin/functions';
|
||||
import { isRoomNameEnabled } from '../../../../prejoin/functions.web';
|
||||
import Toolbox from '../../../../toolbox/components/web/Toolbox';
|
||||
import { isButtonEnabled } from '../../../../toolbox/functions.web';
|
||||
import { getConferenceName } from '../../../conference/functions';
|
||||
|
||||
@@ -291,7 +291,7 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) {
|
||||
const notificationAction = dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.cameraNotSendingData',
|
||||
titleKey: 'dialog.cameraNotSendingDataTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
notificationInfo = {
|
||||
uid: notificationAction?.uid
|
||||
@@ -838,16 +838,6 @@ export function toggleCamera() {
|
||||
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||
const { localFlipX } = state['features/base/settings'];
|
||||
|
||||
/**
|
||||
* FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
|
||||
* but it seems to not trigger the re-rendering of the local video on Chrome;
|
||||
* could be due to a plan B vs unified plan issue. Therefore, we use the legacy
|
||||
* method defined in conference.js that manually takes care of updating the local
|
||||
* video as well.
|
||||
*/
|
||||
await APP.conference.useVideoStream(null);
|
||||
|
||||
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
|
||||
? CAMERA_FACING_MODE.ENVIRONMENT
|
||||
: CAMERA_FACING_MODE.USER;
|
||||
@@ -857,7 +847,6 @@ export function toggleCamera() {
|
||||
|
||||
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
|
||||
|
||||
// FIXME: See above.
|
||||
await APP.conference.useVideoStream(newVideoTrack);
|
||||
dispatch(replaceLocalTrack(localVideoTrack, newVideoTrack));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -157,9 +157,9 @@ async function _toggleScreenSharing(
|
||||
try {
|
||||
tracks = await createLocalTracksF(options) as any[];
|
||||
} catch (error) {
|
||||
dispatch(handleScreenSharingError(error, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(handleScreenSharingError(error));
|
||||
|
||||
throw error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,9 +171,9 @@ async function _toggleScreenSharing(
|
||||
desktopVideoTrack.dispose();
|
||||
|
||||
if (!desktopAudioTrack) {
|
||||
dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(handleScreenSharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK));
|
||||
|
||||
throw new Error(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK);
|
||||
return;
|
||||
}
|
||||
} else if (desktopVideoTrack) {
|
||||
if (localScreenshare) {
|
||||
@@ -457,7 +457,7 @@ export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksE
|
||||
} = errors;
|
||||
|
||||
if (screenSharingError) {
|
||||
dispatch(handleScreenSharingError(screenSharingError, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
dispatch(handleScreenSharingError(screenSharingError));
|
||||
}
|
||||
if (audioOnlyError || videoOnlyError) {
|
||||
if (audioOnlyError) {
|
||||
@@ -476,12 +476,10 @@ export function displayErrorsForCreateInitialLocalTracks(errors: IInitialTracksE
|
||||
*
|
||||
* @private
|
||||
* @param {Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK} error - The error.
|
||||
* @param {NOTIFICATION_TIMEOUT_TYPE} timeout - The time for showing the notification.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function handleScreenSharingError(
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK,
|
||||
timeout: NOTIFICATION_TIMEOUT_TYPE) {
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
logger.error('failed to share local desktop', error);
|
||||
|
||||
@@ -508,6 +506,6 @@ export function handleScreenSharingError(
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
titleKey
|
||||
}, timeout));
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ interface IState {
|
||||
/**
|
||||
* The id of the last read message.
|
||||
*/
|
||||
lastReadMessageId: string;
|
||||
lastReadMessageId: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -163,7 +163,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
||||
this._onVidespaceTouchStart = this._onVidespaceTouchStart.bind(this);
|
||||
this._onVideospaceTouchStart = this._onVideospaceTouchStart.bind(this);
|
||||
this._setBackground = this._setBackground.bind(this);
|
||||
}
|
||||
|
||||
@@ -241,11 +241,11 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { isMobileBrowser() ? undefined : this._onShowToolbar }>
|
||||
<ConferenceInfo />
|
||||
{ _showPrejoin || _showLobby || <ConferenceInfo /> }
|
||||
<Notice />
|
||||
<div
|
||||
id = 'videospace'
|
||||
onTouchStart = { this._onVidespaceTouchStart }>
|
||||
onTouchStart = { this._onVideospaceTouchStart }>
|
||||
<LargeVideo />
|
||||
{
|
||||
_showPrejoin || _showLobby || (<>
|
||||
@@ -322,7 +322,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onVidespaceTouchStart() {
|
||||
_onVideospaceTouchStart() {
|
||||
this.props.dispatch(toggleToolboxVisible());
|
||||
}
|
||||
|
||||
|
||||
@@ -270,7 +270,14 @@ class DesktopPicker extends PureComponent<IProps, IState> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCloseModal(id = '', type?: string, screenShareAudio = false) {
|
||||
this.props.onSourceChoose(id, type, screenShareAudio);
|
||||
// Find the entire source object from the id. We need the name in order
|
||||
// to get getDisplayMedia working in Electron.
|
||||
const { sources } = this.state;
|
||||
|
||||
// @ts-ignore
|
||||
const source = sources.screen.concat(sources.window).find(s => s.id === id);
|
||||
|
||||
this.props.onSourceChoose(id, type, screenShareAudio, source);
|
||||
this.props.dispatch(hideDialog());
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import Platform from '../../../base/react/Platform.native';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { getHideSelfView } from '../../../base/settings/functions.any';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions';
|
||||
import { setVisibleRemoteParticipants } from '../../actions';
|
||||
import { isToolboxVisible } from '../../../toolbox/functions.native';
|
||||
import { setVisibleRemoteParticipants } from '../../actions.native';
|
||||
import {
|
||||
getFilmstripDimensions,
|
||||
isFilmstripVisible,
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
setUserFilmstripWidth,
|
||||
setUserIsResizing,
|
||||
setVisibleRemoteParticipants
|
||||
} from '../../actions';
|
||||
} from '../../actions.web';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
DEFAULT_FILMSTRIP_WIDTH,
|
||||
@@ -39,10 +39,10 @@ import {
|
||||
} from '../../constants';
|
||||
import {
|
||||
getVerticalViewMaxWidth,
|
||||
isFilmstripDisabled,
|
||||
isStageFilmstripTopPanel,
|
||||
shouldRemoteVideosBeVisible
|
||||
} from '../../functions';
|
||||
import { isFilmstripDisabled } from '../../functions.web';
|
||||
} from '../../functions.web';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
import Thumbnail from './Thumbnail';
|
||||
|
||||
@@ -190,7 +190,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
||||
}));
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'addPeople.failedToAdd'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}));
|
||||
} else if (!_callFlowsEnabled) {
|
||||
const invitedCount = invitees.length;
|
||||
let notificationProps: INotificationProps | undefined;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { debounce } from 'lodash-es';
|
||||
import { NativeEventEmitter, NativeModules } from 'react-native';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import { ENDPOINT_TEXT_MESSAGE_NAME } from '../../../../modules/API/constants';
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { IStore } from '../../app/types';
|
||||
@@ -32,8 +32,7 @@ import {
|
||||
JITSI_CONNECTION_URL_KEY
|
||||
} from '../../base/connection/constants';
|
||||
import { getURLWithoutParams } from '../../base/connection/utils';
|
||||
import {
|
||||
JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
|
||||
import { JitsiConferenceEvents, JitsiRecordingConstants } from '../../base/lib-jitsi-meet';
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
|
||||
import { toggleCameraFacingMode } from '../../base/media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../../base/media/constants';
|
||||
@@ -51,6 +50,11 @@ import { getLocalTracks, isLocalTrackMuted } from '../../base/tracks/functions.n
|
||||
import { ITrack } from '../../base/tracks/types';
|
||||
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
|
||||
import { closeChat, openChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.native';
|
||||
import { isEnabled as isDropboxEnabled } from '../../dropbox/functions.native';
|
||||
import { hideNotification, showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../../notifications/constants';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../recording/constants';
|
||||
import { getActiveSession } from '../../recording/functions';
|
||||
import { setRequestingSubtitles } from '../../subtitles/actions.any';
|
||||
import { CUSTOM_BUTTON_PRESSED } from '../../toolbox/actionTypes';
|
||||
import { muteLocal } from '../../video-menu/actions.native';
|
||||
@@ -419,6 +423,158 @@ function _registerForNativeEvents(store: IStore) {
|
||||
eventEmitter.addListener(ExternalAPI.TOGGLE_CAMERA, () => {
|
||||
dispatch(toggleCameraFacingMode());
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SHOW_NOTIFICATION,
|
||||
({ appearance, description, timeout, title, uid }: any) => {
|
||||
const validTypes = Object.values(NOTIFICATION_TYPE);
|
||||
const validTimeouts = Object.values(NOTIFICATION_TIMEOUT_TYPE);
|
||||
|
||||
if (!validTypes.includes(appearance)) {
|
||||
logger.error(`Invalid notification type "${appearance}". Expecting one of ${validTypes}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validTimeouts.includes(timeout)) {
|
||||
logger.error(`Invalid notification timeout "${timeout}". Expecting one of ${validTimeouts}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
appearance,
|
||||
description,
|
||||
title,
|
||||
uid
|
||||
}, timeout));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.HIDE_NOTIFICATION, ({ uid }: any) => {
|
||||
dispatch(hideNotification(uid));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.START_RECORDING, (
|
||||
{
|
||||
mode,
|
||||
dropboxToken,
|
||||
shouldShare,
|
||||
rtmpStreamKey,
|
||||
rtmpBroadcastID,
|
||||
youtubeStreamKey,
|
||||
youtubeBroadcastID,
|
||||
extraMetadata = {},
|
||||
transcription
|
||||
}: any) => {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropboxToken && !isDropboxEnabled(state)) {
|
||||
logger.error('Failed starting recording: dropbox is not enabled on this deployment');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.STREAM && !(youtubeStreamKey || rtmpStreamKey)) {
|
||||
logger.error('Failed starting recording: missing youtube or RTMP stream key');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let recordingConfig;
|
||||
|
||||
if (mode === JitsiRecordingConstants.mode.FILE) {
|
||||
const { recordingService } = state['features/base/config'];
|
||||
|
||||
if (!recordingService?.enabled && !dropboxToken) {
|
||||
logger.error('Failed starting recording: the recording service is not enabled');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (dropboxToken) {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
...extraMetadata,
|
||||
'upload_credentials': {
|
||||
'service_name': RECORDING_TYPES.DROPBOX,
|
||||
'token': dropboxToken
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
} else {
|
||||
recordingConfig = {
|
||||
mode: JitsiRecordingConstants.mode.FILE,
|
||||
appData: JSON.stringify({
|
||||
'file_recording_metadata': {
|
||||
...extraMetadata,
|
||||
'share': shouldShare
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
recordingConfig = {
|
||||
broadcastId: youtubeBroadcastID || rtmpBroadcastID,
|
||||
mode: JitsiRecordingConstants.mode.STREAM,
|
||||
streamId: youtubeStreamKey || rtmpStreamKey
|
||||
};
|
||||
}
|
||||
|
||||
// Start audio / video recording, if requested.
|
||||
if (typeof recordingConfig !== 'undefined') {
|
||||
conference.startRecording(recordingConfig);
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
store.dispatch(setRequestingSubtitles(true, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.STOP_RECORDING, ({ mode, transcription }: any) => {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
logger.error('Conference is not defined');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (transcription) {
|
||||
store.dispatch(setRequestingSubtitles(false, false, null));
|
||||
conference.getMetadataHandler().setMetadata(RECORDING_METADATA_ID, {
|
||||
isTranscribingEnabled: false
|
||||
});
|
||||
}
|
||||
|
||||
if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
|
||||
logger.error('Invalid recording mode provided!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const activeSession = getActiveSession(state, mode);
|
||||
|
||||
if (!activeSession?.id) {
|
||||
logger.error('No recording or streaming session found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.stopRecording(activeSession.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -439,6 +595,10 @@ function _unregisterForNativeEvents() {
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SEND_CHAT_MESSAGE);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SET_CLOSED_CAPTIONS_ENABLED);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.TOGGLE_CAMERA);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.SHOW_NOTIFICATION);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.HIDE_NOTIFICATION);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.START_RECORDING);
|
||||
eventEmitter.removeAllListeners(ExternalAPI.STOP_RECORDING);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,7 +4,6 @@ import { NativeModules, Platform } from 'react-native';
|
||||
import BackgroundTimer from 'react-native-background-timer';
|
||||
import { TextDecoder, TextEncoder } from 'text-encoding';
|
||||
|
||||
import 'promise.allsettled/auto'; // Promise.allSettled.
|
||||
import 'promise.withresolvers/auto'; // Promise.withResolvers.
|
||||
import 'react-native-url-polyfill/auto'; // Complete URL polyfill.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { getLocalJitsiAudioTrack } from '../base/tracks/functions';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { NoiseSuppressionEffect } from '../stream-effects/noise-suppression/NoiseSuppressionEffect';
|
||||
|
||||
import { SET_NOISE_SUPPRESSION_ENABLED } from './actionTypes';
|
||||
@@ -93,7 +92,7 @@ export function setNoiseSuppressionEnabled(enabled: boolean): any {
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'notify.noiseSuppressionFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export function setNotificationsEnabled(enabled: boolean) {
|
||||
* @param {string} type - Notification type.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function showErrorNotification(props: INotificationProps, type?: string) {
|
||||
export function showErrorNotification(props: INotificationProps, type = NOTIFICATION_TIMEOUT_TYPE.STICKY) {
|
||||
return showNotification({
|
||||
...props,
|
||||
appearance: NOTIFICATION_TYPE.ERROR
|
||||
|
||||
@@ -126,10 +126,3 @@ export const SILENT_JOIN_THRESHOLD = 30;
|
||||
* Amount of participants beyond which no left notification will be emitted.
|
||||
*/
|
||||
export const SILENT_LEFT_THRESHOLD = 30;
|
||||
|
||||
/**
|
||||
* The identifier for the transcriber notifications.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const TRANSCRIBING_NOTIFICATION_ID = 'TRANSCRIBING_NOTIFICATION';
|
||||
|
||||
@@ -5,7 +5,6 @@ import { IStore } from '../app/types';
|
||||
import { APP_WILL_MOUNT } from '../base/app/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
|
||||
import OldElectronAPPNotificationDescription from './components/OldElectronAPPNotificationDescription';
|
||||
import { isOldJitsiMeetElectronApp } from './functions';
|
||||
@@ -35,7 +34,7 @@ function _appWillMount(store: IStore, next: Function, action: AnyAction) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'notify.OldElectronAPPTitle',
|
||||
description: <OldElectronAPPNotificationDescription />
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
import { openURLInBrowser } from '../base/util/openURLInBrowser';
|
||||
import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { INotificationProps } from '../notifications/types';
|
||||
|
||||
import {
|
||||
@@ -108,7 +107,7 @@ function pollForStatus(
|
||||
case DIAL_OUT_STATUS.DISCONNECTED: {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'prejoin.errorDialOutDisconnected'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return onFail();
|
||||
}
|
||||
@@ -116,7 +115,7 @@ function pollForStatus(
|
||||
case DIAL_OUT_STATUS.FAILED: {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'prejoin.errorDialOutFailed'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return onFail();
|
||||
}
|
||||
@@ -124,7 +123,7 @@ function pollForStatus(
|
||||
} catch (err) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'prejoin.errorDialOutStatus'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
logger.error('Error getting dial out status', err);
|
||||
onFail();
|
||||
}
|
||||
@@ -177,7 +176,7 @@ export function dialOut(onSuccess: Function, onFail: Function) {
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification(notification, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
dispatch(showErrorNotification(notification));
|
||||
logger.error('Error dialing out', err);
|
||||
onFail();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import RaiseHandButton from '../../../toolbox/components/native/RaiseHandButton';
|
||||
import { shouldDisplayReactionsButtons } from '../../functions.native';
|
||||
|
||||
import ReactionsMenuButton from './ReactionsMenuButton';
|
||||
|
||||
const RaiseHandContainerButtons = (props: AbstractButtonProps) => {
|
||||
const _shouldDisplayReactionsButtons = useSelector(shouldDisplayReactionsButtons);
|
||||
|
||||
return _shouldDisplayReactionsButtons
|
||||
? <ReactionsMenuButton
|
||||
{ ...props }
|
||||
showRaiseHand = { true } />
|
||||
: <RaiseHandButton { ...props } />;
|
||||
};
|
||||
|
||||
export default RaiseHandContainerButtons;
|
||||
@@ -189,7 +189,7 @@ export function highlightMeetingMoment() {
|
||||
* @returns {showErrorNotification}
|
||||
*/
|
||||
export function showRecordingError(props: Object) {
|
||||
return showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG);
|
||||
return showErrorNotification(props);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +301,7 @@ export function showStartedRecordingNotification(
|
||||
} catch (err) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'recording.errorFetchingLink'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}));
|
||||
|
||||
return logger.error('Could not fetch recording link', err);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
|
||||
import { updateDropboxToken } from '../../../dropbox/actions';
|
||||
import { getDropboxData, getNewAccessToken, isEnabled as isDropboxEnabled } from '../../../dropbox/functions.any';
|
||||
import { showErrorNotification } from '../../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
|
||||
import { setRequestingSubtitles } from '../../../subtitles/actions.any';
|
||||
import { setSelectedRecordingService, startLocalVideoRecording } from '../../actions';
|
||||
import { RECORDING_METADATA_ID, RECORDING_TYPES } from '../../constants';
|
||||
@@ -381,7 +380,7 @@ class AbstractStartRecordingDialog extends Component<IProps, IState> {
|
||||
} else {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'dialog.noDropboxToken'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ class StopRecordingDialog extends AbstractStopRecordingDialog<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
|
||||
/**
|
||||
* Toggles screenshot capture.
|
||||
*
|
||||
|
||||
@@ -68,8 +68,12 @@ class RecordingExpandedLabel extends ExpandedLabel<IProps> {
|
||||
|
||||
let content = t(`${prefix}.${postfix}`);
|
||||
|
||||
if (_status === JitsiRecordingConstants.status.ON && this.props._isTranscribing) {
|
||||
content += ` \u00B7 ${t('transcribing.labelToolTip')}`;
|
||||
if (this.props._isTranscribing) {
|
||||
if (_status === JitsiRecordingConstants.status.ON) {
|
||||
content += ` ${t('transcribing.labelTooltipExtra')}`;
|
||||
} else {
|
||||
content = t('transcribing.labelTooltip');
|
||||
}
|
||||
}
|
||||
|
||||
return content;
|
||||
|
||||
@@ -60,12 +60,12 @@ class RecordingLabel extends AbstractRecordingLabel<IProps> {
|
||||
content = t(isRecording ? 'videoStatus.recording' : 'videoStatus.streaming');
|
||||
|
||||
if (_isTranscribing) {
|
||||
content += ` \u00B7 ${t('transcribing.labelToolTip')}`;
|
||||
content += ` ${t('transcribing.labelTooltipExtra')}`;
|
||||
}
|
||||
} else if (mode === JitsiRecordingConstants.mode.STREAM) {
|
||||
return null;
|
||||
} else if (_isTranscribing) {
|
||||
content = t('transcribing.labelToolTip');
|
||||
content = t('transcribing.labelTooltip');
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
false, 'local', err.message, isRecorderTranscriptionsRunning(getState()));
|
||||
}
|
||||
|
||||
dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(showErrorNotification(props));
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -204,10 +204,14 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
= getSessionById(state, action.sessionData.id);
|
||||
const { initiator, mode = '', terminator } = updatedSessionData ?? {};
|
||||
const { PENDING, OFF, ON } = JitsiRecordingConstants.status;
|
||||
const isRecordingStarting = updatedSessionData?.status === PENDING && oldSessionData?.status !== PENDING;
|
||||
|
||||
if (updatedSessionData?.status === PENDING && oldSessionData?.status !== PENDING) {
|
||||
dispatch(showPendingRecordingNotification(mode));
|
||||
if (isRecordingStarting || updatedSessionData?.status === ON) {
|
||||
dispatch(hideNotification(START_RECORDING_NOTIFICATION_ID));
|
||||
}
|
||||
|
||||
if (isRecordingStarting) {
|
||||
dispatch(showPendingRecordingNotification(mode));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ class PasswordRequiredPrompt extends Component<IProps, IState> {
|
||||
|
||||
// We have used the password so let's clean it.
|
||||
this.setState({
|
||||
password: undefined
|
||||
password: ''
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -142,7 +142,7 @@ function _setPasswordFailed(store: IStore, next: Function, action: AnyAction) {
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
titleKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
||||
@@ -6,9 +6,9 @@ import { timeout } from '../../virtual-background/functions';
|
||||
import logger from '../../virtual-background/logger';
|
||||
|
||||
import JitsiStreamBackgroundEffect, { IBackgroundEffectOptions } from './JitsiStreamBackgroundEffect';
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import createTFLiteModule from './vendor/tflite/tflite';
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import createTFLiteSIMDModule from './vendor/tflite/tflite-simd';
|
||||
const models = {
|
||||
modelLandscape: 'libs/selfie_segmentation_landscape.tflite'
|
||||
|
||||
@@ -13,9 +13,5 @@ var Module=typeof createTFLiteSIMDModule!=="undefined"?createTFLiteSIMDModule:{}
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = createTFLiteSIMDModule;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return createTFLiteSIMDModule; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["createTFLiteSIMDModule"] = createTFLiteSIMDModule;
|
||||
|
||||
export default createTFLiteSIMDModule;
|
||||
|
||||
@@ -13,9 +13,5 @@ var Module=typeof createTFLiteModule!=="undefined"?createTFLiteModule:{};var rea
|
||||
}
|
||||
);
|
||||
})();
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = createTFLiteModule;
|
||||
else if (typeof define === 'function' && define['amd'])
|
||||
define([], function() { return createTFLiteModule; });
|
||||
else if (typeof exports === 'object')
|
||||
exports["createTFLiteModule"] = createTFLiteModule;
|
||||
|
||||
export default createTFLiteModule;
|
||||
|
||||
@@ -6,11 +6,13 @@ import { setVideoMuted } from '../base/media/actions';
|
||||
import { VIDEO_MUTISM_AUTHORITY } from '../base/media/constants';
|
||||
|
||||
import {
|
||||
SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
SET_TOOLBOX_ENABLED,
|
||||
SET_TOOLBOX_SHIFT_UP,
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
import { IMainToolbarButtonThresholds } from './types';
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbox.
|
||||
@@ -118,3 +120,54 @@ export function setShiftUp(shiftUp: boolean) {
|
||||
shiftUp
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mainToolbarButtonsThresholds.
|
||||
*
|
||||
* @param {IMainToolbarButtonThresholds} thresholds - Thresholds for screen size and visible main toolbar buttons.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setMainToolbarThresholds(thresholds: IMainToolbarButtonThresholds) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { mainToolbarButtons } = getState()['features/base/config'];
|
||||
|
||||
if (!Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = [];
|
||||
|
||||
const mainToolbarButtonsLengthMap = new Map();
|
||||
let orderIsChanged = false;
|
||||
|
||||
mainToolbarButtons.forEach(buttons => {
|
||||
if (!Array.isArray(buttons) || buttons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainToolbarButtonsLengthMap.set(buttons.length, buttons);
|
||||
});
|
||||
|
||||
thresholds.forEach(({ width, order }) => {
|
||||
let finalOrder = mainToolbarButtonsLengthMap.get(order.length);
|
||||
|
||||
if (finalOrder) {
|
||||
orderIsChanged = true;
|
||||
} else {
|
||||
finalOrder = order;
|
||||
}
|
||||
|
||||
mainToolbarButtonsThresholds.push({
|
||||
order: finalOrder,
|
||||
width
|
||||
});
|
||||
});
|
||||
|
||||
if (orderIsChanged) {
|
||||
dispatch({
|
||||
type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
mainToolbarButtonsThresholds
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export function setOverflowMenuVisible(_visible: boolean): any {
|
||||
* text: string
|
||||
* }}
|
||||
*/
|
||||
export function customButtonPressed(id: string, text: string) {
|
||||
export function customButtonPressed(id: string, text: string | undefined) {
|
||||
return {
|
||||
type: CUSTOM_BUTTON_PRESSED,
|
||||
id,
|
||||
|
||||
@@ -8,16 +8,13 @@ import {
|
||||
FULL_SCREEN_CHANGED,
|
||||
SET_FULL_SCREEN,
|
||||
SET_HANGUP_MENU_VISIBLE,
|
||||
SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
SET_OVERFLOW_DRAWER,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
SET_TOOLBOX_TIMEOUT
|
||||
} from './actionTypes';
|
||||
import { setToolboxVisible } from './actions.web';
|
||||
import { THRESHOLDS } from './constants';
|
||||
import { getToolbarTimeout } from './functions.web';
|
||||
import { IMainToolbarButtonThresholds } from './types';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -124,56 +121,6 @@ export function setFullScreen(fullScreen: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mainToolbarButtonsThresholds.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function setMainToolbarThresholds() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { mainToolbarButtons } = getState()['features/base/config'];
|
||||
|
||||
if (!mainToolbarButtons || !Array.isArray(mainToolbarButtons) || mainToolbarButtons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainToolbarButtonsThresholds: IMainToolbarButtonThresholds = [];
|
||||
|
||||
const mainToolbarButtonsLenghtMap = new Map();
|
||||
let orderIsChanged = false;
|
||||
|
||||
mainToolbarButtons.forEach(buttons => {
|
||||
if (!Array.isArray(buttons) || buttons.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mainToolbarButtonsLenghtMap.set(buttons.length, buttons);
|
||||
});
|
||||
|
||||
THRESHOLDS.forEach(({ width, order }) => {
|
||||
let finalOrder = mainToolbarButtonsLenghtMap.get(order.length);
|
||||
|
||||
if (finalOrder) {
|
||||
orderIsChanged = true;
|
||||
} else {
|
||||
finalOrder = order;
|
||||
}
|
||||
|
||||
mainToolbarButtonsThresholds.push({
|
||||
order: finalOrder,
|
||||
width
|
||||
});
|
||||
});
|
||||
|
||||
if (orderIsChanged) {
|
||||
dispatch({
|
||||
type: SET_MAIN_TOOLBAR_BUTTONS_THRESHOLDS,
|
||||
mainToolbarButtonsThresholds
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the toolbox for specified timeout.
|
||||
*
|
||||
|
||||
@@ -10,12 +10,12 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
export interface ICustomOptionButton extends AbstractButtonProps {
|
||||
backgroundColor?: string;
|
||||
icon: any;
|
||||
id?: string;
|
||||
isToolboxButton?: boolean;
|
||||
text?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +23,7 @@ interface IProps extends AbstractButtonProps {
|
||||
*
|
||||
* @returns {Component}
|
||||
*/
|
||||
class CustomOptionButton extends AbstractButton<IProps> {
|
||||
class CustomOptionButton extends AbstractButton<ICustomOptionButton> {
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
iconSrc = this.props.icon;
|
||||
id = this.props.id;
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
|
||||
import HangupButton from '../HangupButton';
|
||||
|
||||
import HangupMenuButton from './HangupMenuButton';
|
||||
|
||||
const HangupContainerButtons = (props: AbstractButtonProps) => {
|
||||
const { conference } = useSelector((state: IReduxState) => state['features/base/conference']);
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported();
|
||||
|
||||
return endConferenceSupported
|
||||
|
||||
// @ts-ignore
|
||||
? <HangupMenuButton { ...props } />
|
||||
: <HangupButton { ...props } />;
|
||||
};
|
||||
|
||||
export default HangupContainerButtons;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { ViewStyle } from 'react-native';
|
||||
import { Divider } from 'react-native-paper';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { hideSheet } from '../../../base/dialog/actions';
|
||||
@@ -22,18 +22,17 @@ import { isSharedVideoEnabled } from '../../../shared-video/functions';
|
||||
import SpeakerStatsButton from '../../../speaker-stats/components/native/SpeakerStatsButton';
|
||||
import { isSpeakerStatsDisabled } from '../../../speaker-stats/functions';
|
||||
import ClosedCaptionButton from '../../../subtitles/components/native/ClosedCaptionButton';
|
||||
import TileViewButton from '../../../video-layout/components/TileViewButton';
|
||||
import styles from '../../../video-menu/components/native/styles';
|
||||
import WhiteboardButton from '../../../whiteboard/components/native/WhiteboardButton';
|
||||
import { customButtonPressed } from '../../actions.native';
|
||||
import { getMovableButtons } from '../../functions.native';
|
||||
import { getVisibleNativeButtons } from '../../functions.native';
|
||||
import { useNativeToolboxButtons } from '../../hooks.native';
|
||||
import { IToolboxNativeButton } from '../../types';
|
||||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
import LinkToSalesforceButton from './LinkToSalesforceButton';
|
||||
import OpenCarmodeButton from './OpenCarmodeButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ScreenSharingButton from './ScreenSharingButton';
|
||||
|
||||
|
||||
/**
|
||||
@@ -41,11 +40,6 @@ import ScreenSharingButton from './ScreenSharingButton';
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* True if breakout rooms feature is available, false otherwise.
|
||||
*/
|
||||
@@ -66,6 +60,16 @@ interface IProps {
|
||||
*/
|
||||
_isSpeakerStatsDisabled?: boolean;
|
||||
|
||||
/**
|
||||
* Toolbar buttons.
|
||||
*/
|
||||
_mainMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Overflow menu buttons.
|
||||
*/
|
||||
_overflowMenuButtons?: Array<IToolboxNativeButton>;
|
||||
|
||||
/**
|
||||
* Whether the recoding button should be enabled or not.
|
||||
*/
|
||||
@@ -76,11 +80,6 @@ interface IProps {
|
||||
*/
|
||||
_shouldDisplayReactionsButtons: boolean;
|
||||
|
||||
/**
|
||||
* The width of the screen.
|
||||
*/
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Used for hiding the dialog when the selection was completed.
|
||||
*/
|
||||
@@ -128,11 +127,8 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
_isBreakoutRoomsSupported,
|
||||
_isSpeakerStatsDisabled,
|
||||
_isSharedVideoEnabled,
|
||||
_shouldDisplayReactionsButtons,
|
||||
_width,
|
||||
dispatch
|
||||
} = this.props;
|
||||
const toolbarButtons = getMovableButtons(_width);
|
||||
|
||||
const buttonProps = {
|
||||
afterClick: this._onCancel,
|
||||
@@ -156,18 +152,12 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
renderFooter = { _shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
|
||||
? this._renderReactionMenu
|
||||
: undefined }>
|
||||
{ this._renderCustomOverflowMenuButtons(topButtonProps) }
|
||||
renderFooter = { this._renderReactionMenu }>
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<OpenCarmodeButton { ...topButtonProps } />
|
||||
<AudioOnlyButton { ...buttonProps } />
|
||||
{
|
||||
!_shouldDisplayReactionsButtons && !toolbarButtons.has('raisehand')
|
||||
&& <RaiseHandButton { ...buttonProps } />
|
||||
}
|
||||
{ this._renderRaiseHandButton(buttonProps) }
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
<SecurityDialogButton { ...buttonProps } />
|
||||
<RecordButton { ...buttonProps } />
|
||||
<LiveStreamButton { ...buttonProps } />
|
||||
@@ -176,9 +166,8 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
{_isSharedVideoEnabled && <SharedVideoButton { ...buttonProps } />}
|
||||
{!toolbarButtons.has('screensharing') && <ScreenSharingButton { ...buttonProps } />}
|
||||
{ this._renderOverflowMenuButtons(topButtonProps) }
|
||||
{!_isSpeakerStatsDisabled && <SpeakerStatsButton { ...buttonProps } />}
|
||||
{!toolbarButtons.has('tileview') && <TileViewButton { ...buttonProps } />}
|
||||
{_isBreakoutRoomsSupported && <BreakoutRoomsButton { ...buttonProps } />}
|
||||
{/* @ts-ignore */}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
@@ -205,42 +194,72 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderReactionMenu() {
|
||||
return (
|
||||
<ReactionMenu
|
||||
onCancel = { this._onCancel }
|
||||
overflowMenu = { true } />
|
||||
);
|
||||
const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand');
|
||||
|
||||
if (_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
|
||||
return (
|
||||
<ReactionMenu
|
||||
onCancel = { this._onCancel }
|
||||
overflowMenu = { true } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to render the reaction menu as the footer of the bottom sheet.
|
||||
*
|
||||
* @param {Object} buttonProps - Styling button properties.
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderRaiseHandButton(buttonProps: Object) {
|
||||
const { _mainMenuButtons, _shouldDisplayReactionsButtons } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
const isRaiseHandInMainMenu = _mainMenuButtons?.some(item => item.key === 'raisehand');
|
||||
|
||||
if (!_shouldDisplayReactionsButtons && !isRaiseHandInMainMenu) {
|
||||
return (
|
||||
<RaiseHandButton { ...buttonProps } />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to render the custom buttons for the overflow menu.
|
||||
*
|
||||
* @param {Object} topButtonProps - Button properties.
|
||||
* @param {Object} topButtonProps - Styling button properties.
|
||||
* @returns {React.ReactElement}
|
||||
*/
|
||||
_renderCustomOverflowMenuButtons(topButtonProps: Object) {
|
||||
const { _customToolbarButtons, dispatch } = this.props;
|
||||
_renderOverflowMenuButtons(topButtonProps: Object) {
|
||||
const { _overflowMenuButtons, dispatch } = this.props;
|
||||
|
||||
if (!_customToolbarButtons?.length) {
|
||||
if (!_overflowMenuButtons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
_customToolbarButtons.map(({ id, text, icon, backgroundColor }) => (
|
||||
<CustomOptionButton
|
||||
{ ...topButtonProps }
|
||||
backgroundColor = { backgroundColor }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () =>
|
||||
dispatch(customButtonPressed(id, text))
|
||||
}
|
||||
icon = { icon }
|
||||
isToolboxButton = { false }
|
||||
key = { id }
|
||||
text = { text } />
|
||||
))
|
||||
_overflowMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => {
|
||||
|
||||
if (key === 'raisehand') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Content
|
||||
{ ...topButtonProps }
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(key, text)) }
|
||||
isToolboxButton = { false }
|
||||
key = { key }
|
||||
text = { text } />
|
||||
);
|
||||
})
|
||||
}
|
||||
<Divider style = { styles.divider as ViewStyle } />
|
||||
</>
|
||||
@@ -257,16 +276,38 @@ class OverflowMenu extends PureComponent<IProps, IState> {
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { customToolbarButtons } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_customToolbarButtons: customToolbarButtons,
|
||||
_isBreakoutRoomsSupported: conference?.getBreakoutRooms()?.isSupported(),
|
||||
_isSharedVideoEnabled: isSharedVideoEnabled(state),
|
||||
_isSpeakerStatsDisabled: isSpeakerStatsDisabled(state),
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state),
|
||||
_width: state['features/base/responsive-ui'].clientWidth
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(OverflowMenu);
|
||||
export default connect(_mapStateToProps)(props => {
|
||||
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
|
||||
const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const {
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
} = useSelector((state: IReduxState) => state['features/toolbox']);
|
||||
|
||||
const allButtons = useNativeToolboxButtons(customToolbarButtons);
|
||||
|
||||
const { mainMenuButtons, overflowMenuButtons } = getVisibleNativeButtons({
|
||||
allButtons,
|
||||
clientWidth,
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
return (
|
||||
<OverflowMenu
|
||||
|
||||
// @ts-ignore
|
||||
{ ... props }
|
||||
_mainMenuButtons = { mainMenuButtons }
|
||||
_overflowMenuButtons = { overflowMenuButtons } />
|
||||
);
|
||||
});
|
||||
|
||||
@@ -25,6 +25,8 @@ class OverflowMenuButton extends AbstractButton<AbstractButtonProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_handleClick() {
|
||||
|
||||
// @ts-ignore
|
||||
this.props.dispatch(openSheet(OverflowMenu));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,29 @@
|
||||
import React from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { connect } from 'react-redux';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import ColorSchemeRegistry from '../../../base/color-scheme/ColorSchemeRegistry';
|
||||
import Platform from '../../../base/react/Platform.native';
|
||||
import ChatButton from '../../../chat/components/native/ChatButton';
|
||||
import ReactionsMenuButton from '../../../reactions/components/native/ReactionsMenuButton';
|
||||
import { shouldDisplayReactionsButtons } from '../../../reactions/functions.any';
|
||||
import TileViewButton from '../../../video-layout/components/TileViewButton';
|
||||
import { iAmVisitor } from '../../../visitors/functions';
|
||||
import { customButtonPressed } from '../../actions.native';
|
||||
import { getMovableButtons, isToolboxVisible } from '../../functions.native';
|
||||
import HangupButton from '../HangupButton';
|
||||
import { getVisibleNativeButtons, isToolboxVisible } from '../../functions.native';
|
||||
import { useNativeToolboxButtons } from '../../hooks.native';
|
||||
import { IToolboxNativeButton } from '../../types';
|
||||
|
||||
import AudioMuteButton from './AudioMuteButton';
|
||||
import CustomOptionButton from './CustomOptionButton';
|
||||
import HangupMenuButton from './HangupMenuButton';
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import ScreenSharingButton from './ScreenSharingButton';
|
||||
import VideoMuteButton from './VideoMuteButton';
|
||||
import styles from './styles';
|
||||
|
||||
|
||||
/**
|
||||
* The type of {@link Toolbox}'s React {@code Component} props.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* Whether the end conference feature is supported.
|
||||
*/
|
||||
_endConferenceSupported: boolean;
|
||||
|
||||
/**
|
||||
* Whether we are in visitors mode.
|
||||
*/
|
||||
_iAmVisitor: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not any reactions buttons should be visible.
|
||||
*/
|
||||
_shouldDisplayReactionsButtons: boolean;
|
||||
|
||||
/**
|
||||
* The color-schemed stylesheet of the feature.
|
||||
*/
|
||||
@@ -60,11 +34,6 @@ interface IProps {
|
||||
*/
|
||||
_visible: boolean;
|
||||
|
||||
/**
|
||||
* The width of the screen.
|
||||
*/
|
||||
_width: number;
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
@@ -79,13 +48,9 @@ interface IProps {
|
||||
*/
|
||||
function Toolbox(props: IProps) {
|
||||
const {
|
||||
_customToolbarButtons,
|
||||
_endConferenceSupported,
|
||||
_iAmVisitor,
|
||||
_shouldDisplayReactionsButtons,
|
||||
_styles,
|
||||
_visible,
|
||||
_width,
|
||||
dispatch
|
||||
} = props;
|
||||
|
||||
@@ -93,41 +58,47 @@ function Toolbox(props: IProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { clientWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
|
||||
const { customToolbarButtons } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const {
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
} = useSelector((state: IReduxState) => state['features/toolbox']);
|
||||
|
||||
const allButtons = useNativeToolboxButtons(customToolbarButtons);
|
||||
|
||||
const { mainMenuButtons } = getVisibleNativeButtons({
|
||||
allButtons,
|
||||
clientWidth,
|
||||
mainToolbarButtonsThresholds,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
const bottomEdge = Platform.OS === 'ios' && _visible;
|
||||
const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles;
|
||||
const additionalButtons = getMovableButtons(_width);
|
||||
const backgroundToggledStyle = {
|
||||
...toggledButtonStyles,
|
||||
style: [
|
||||
toggledButtonStyles.style,
|
||||
_styles.backgroundToggle
|
||||
]
|
||||
};
|
||||
const { buttonStylesBorderless, hangupButtonStyles } = _styles;
|
||||
const style = { ...styles.toolbox };
|
||||
|
||||
// we have only hangup and raisehand button in _iAmVisitor mode
|
||||
// We have only hangup and raisehand button in _iAmVisitor mode
|
||||
if (_iAmVisitor) {
|
||||
additionalButtons.add('raisehand');
|
||||
style.justifyContent = 'center';
|
||||
}
|
||||
|
||||
const renderCustomToolboxButtons = () => {
|
||||
if (!_customToolbarButtons?.length) {
|
||||
const renderToolboxButtons = () => {
|
||||
if (!mainMenuButtons?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
_customToolbarButtons.map(({ backgroundColor, id, text, icon }) => (
|
||||
<CustomOptionButton
|
||||
backgroundColor = { backgroundColor }
|
||||
|
||||
mainMenuButtons?.map(({ Content, key, text, ...rest }: IToolboxNativeButton) => (
|
||||
<Content
|
||||
{ ...rest }
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
handleClick = { () => dispatch(customButtonPressed(id, text)) }
|
||||
icon = { icon }
|
||||
handleClick = { () => dispatch(customButtonPressed(key, text)) }
|
||||
isToolboxButton = { true }
|
||||
key = { id } />
|
||||
key = { key }
|
||||
styles = { key === 'hangup' ? hangupButtonStyles : buttonStylesBorderless } />
|
||||
))
|
||||
}
|
||||
</>
|
||||
@@ -144,44 +115,7 @@ function Toolbox(props: IProps) {
|
||||
edges = { [ bottomEdge && 'bottom' ].filter(Boolean) }
|
||||
pointerEvents = 'box-none'
|
||||
style = { style as ViewStyle }>
|
||||
{
|
||||
_customToolbarButtons
|
||||
? <>
|
||||
{ renderCustomToolboxButtons() }
|
||||
{ !_iAmVisitor && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } /> }
|
||||
</>
|
||||
: <>
|
||||
{!_iAmVisitor && <AudioMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{!_iAmVisitor && <VideoMuteButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{additionalButtons.has('chat')
|
||||
&& <ChatButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />}
|
||||
{!_iAmVisitor && additionalButtons.has('screensharing')
|
||||
&& <ScreenSharingButton styles = { buttonStylesBorderless } />}
|
||||
{additionalButtons.has('raisehand') && (_shouldDisplayReactionsButtons
|
||||
? <ReactionsMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />
|
||||
: <RaiseHandButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { backgroundToggledStyle } />)}
|
||||
{additionalButtons.has('tileview')
|
||||
&& <TileViewButton styles = { buttonStylesBorderless } />}
|
||||
{!_iAmVisitor && <OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />}
|
||||
{ _endConferenceSupported
|
||||
? <HangupMenuButton />
|
||||
: <HangupButton styles = { hangupButtonStyles } />}
|
||||
</>
|
||||
}
|
||||
{ renderToolboxButtons() }
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
@@ -197,17 +131,10 @@ function Toolbox(props: IProps) {
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported();
|
||||
|
||||
return {
|
||||
_customToolbarButtons: state['features/base/config']?.customToolbarButtons,
|
||||
_endConferenceSupported: Boolean(endConferenceSupported),
|
||||
_iAmVisitor: iAmVisitor(state),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
|
||||
_visible: isToolboxVisible(state),
|
||||
_iAmVisitor: iAmVisitor(state),
|
||||
_width: state['features/base/responsive-ui'].clientWidth,
|
||||
_shouldDisplayReactionsButtons: shouldDisplayReactionsButtons(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ToolbarButton } from './types';
|
||||
import { NativeToolbarButton, ToolbarButton } from './types';
|
||||
|
||||
/**
|
||||
* Thresholds for displaying toolbox buttons.
|
||||
@@ -34,6 +34,32 @@ export const THRESHOLDS = [
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Thresholds for displaying native toolbox buttons.
|
||||
*/
|
||||
export const NATIVE_THRESHOLDS = [
|
||||
{
|
||||
width: 560,
|
||||
order: [ 'microphone', 'camera', 'chat', 'screensharing', 'raisehand', 'tileview', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 500,
|
||||
order: [ 'microphone', 'camera', 'chat', 'raisehand', 'tileview', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 440,
|
||||
order: [ 'microphone', 'camera', 'chat', 'raisehand', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 380,
|
||||
order: [ 'microphone', 'camera', 'chat', 'overflowmenu', 'hangup' ]
|
||||
},
|
||||
{
|
||||
width: 320,
|
||||
order: [ 'microphone', 'camera', 'overflowmenu', 'hangup' ]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Main toolbar buttons priority used to determine which button should be picked to fill empty spaces for disabled
|
||||
* buttons.
|
||||
@@ -47,6 +73,8 @@ export const MAIN_TOOLBAR_BUTTONS_PRIORITY = [
|
||||
'reactions',
|
||||
'participants-pane',
|
||||
'tileview',
|
||||
'overflowmenu',
|
||||
'hangup',
|
||||
'invite',
|
||||
'toggle-camera',
|
||||
'videoquality',
|
||||
@@ -128,17 +156,34 @@ export const TOOLBAR_BUTTONS: ToolbarButton[] = [
|
||||
'whiteboard'
|
||||
];
|
||||
|
||||
/**
|
||||
* The list of all possible native buttons.
|
||||
*
|
||||
* @protected
|
||||
* @type Array<string>
|
||||
*/
|
||||
export const NATIVE_TOOLBAR_BUTTONS: NativeToolbarButton[] = [
|
||||
'camera',
|
||||
'chat',
|
||||
'hangup',
|
||||
'microphone',
|
||||
'overflowmenu',
|
||||
'raisehand',
|
||||
'screensharing',
|
||||
'tileview'
|
||||
];
|
||||
|
||||
/**
|
||||
* The toolbar buttons to show when in visitors mode.
|
||||
*/
|
||||
export const VISITORS_MODE_BUTTONS: ToolbarButton[] = [
|
||||
'chat',
|
||||
'closedcaptions',
|
||||
'fullscreen',
|
||||
'hangup',
|
||||
'raisehand',
|
||||
'settings',
|
||||
'tileview',
|
||||
'fullscreen',
|
||||
'stats',
|
||||
'tileview',
|
||||
'videoquality'
|
||||
];
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isJwtFeatureEnabledStateless } from '../base/jwt/functions';
|
||||
import { IGUMPendingState } from '../base/media/types';
|
||||
import { IParticipantFeatures } from '../base/participants/types';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import { VISITORS_MODE_BUTTONS } from './constants';
|
||||
|
||||
/**
|
||||
* Indicates if the audio mute button is disabled or not.
|
||||
*
|
||||
@@ -57,3 +61,41 @@ export function getJwtDisabledButtons(
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled toolbar buttons.
|
||||
*
|
||||
* @param {Object|Function} stateful - Either the whole Redux state object or the Redux store's {@code getState} method.
|
||||
* @param {string[]} definedToolbarButtons - The list of all possible buttons.
|
||||
*
|
||||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
export function getToolbarButtons(stateful: IStateful, definedToolbarButtons: string[]): Array<string> {
|
||||
const state = toState(stateful);
|
||||
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => id);
|
||||
let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : definedToolbarButtons;
|
||||
|
||||
if (iAmVisitor(state)) {
|
||||
buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1);
|
||||
}
|
||||
|
||||
if (customButtons) {
|
||||
return [ ...buttons, ...customButtons ];
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified button is enabled.
|
||||
*
|
||||
* @param {string} buttonName - The name of the button. See {@link interfaceConfig}.
|
||||
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
|
||||
* @returns {boolean} - True if the button is enabled and false otherwise.
|
||||
*/
|
||||
export function isButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
|
||||
const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || [];
|
||||
|
||||
return buttons.includes(buttonName);
|
||||
}
|
||||
|
||||
@@ -7,53 +7,12 @@ import { getParticipantCountWithFake } from '../base/participants/functions';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import { isLocalVideoTrackDesktop } from '../base/tracks/functions.native';
|
||||
|
||||
import { MAIN_TOOLBAR_BUTTONS_PRIORITY } from './constants';
|
||||
import { isButtonEnabled } from './functions.any';
|
||||
import { IGetVisibleNativeButtonsParams, IToolboxNativeButton } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
const WIDTH = {
|
||||
FIT_9_ICONS: 560,
|
||||
FIT_8_ICONS: 500,
|
||||
FIT_7_ICONS: 440,
|
||||
FIT_6_ICONS: 380
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a set of the buttons that are shown in the toolbar
|
||||
* but removed from the overflow menu, based on the width of the screen.
|
||||
*
|
||||
* @param {number} width - The width of the screen.
|
||||
* @returns {Set}
|
||||
*/
|
||||
export function getMovableButtons(width: number): Set<string> {
|
||||
let buttons: string[] = [];
|
||||
|
||||
switch (true) {
|
||||
case width >= WIDTH.FIT_9_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera', 'screensharing', 'raisehand', 'tileview' ];
|
||||
break;
|
||||
}
|
||||
case width >= WIDTH.FIT_8_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera', 'raisehand', 'tileview' ];
|
||||
break;
|
||||
}
|
||||
|
||||
case width >= WIDTH.FIT_7_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera', 'raisehand' ];
|
||||
break;
|
||||
}
|
||||
|
||||
case width >= WIDTH.FIT_6_ICONS: {
|
||||
buttons = [ 'chat', 'togglecamera' ];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
buttons = [ 'chat' ];
|
||||
}
|
||||
}
|
||||
|
||||
return new Set(buttons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the desktop share button is disabled or not.
|
||||
*
|
||||
@@ -99,3 +58,64 @@ export function isVideoMuteButtonDisabled(state: IReduxState) {
|
||||
return !hasAvailableDevices(state, 'videoInput')
|
||||
|| (unmuteBlocked && Boolean(muted));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all buttons that need to be rendered.
|
||||
*
|
||||
* @param {IGetVisibleButtonsParams} params - The parameters needed to extract the visible buttons.
|
||||
* @returns {Object} - The visible buttons arrays .
|
||||
*/
|
||||
export function getVisibleNativeButtons({ allButtons, clientWidth, mainToolbarButtonsThresholds, toolbarButtons
|
||||
}: IGetVisibleNativeButtonsParams) {
|
||||
const filteredButtons = Object.keys(allButtons).filter(key =>
|
||||
typeof key !== 'undefined' // filter invalid buttons that may be coming from config.mainToolbarButtons override
|
||||
&& isButtonEnabled(key, toolbarButtons));
|
||||
|
||||
const { order } = mainToolbarButtonsThresholds.find(({ width }) => clientWidth > width)
|
||||
|| mainToolbarButtonsThresholds[mainToolbarButtonsThresholds.length - 1];
|
||||
|
||||
const mainToolbarButtonKeysOrder = [
|
||||
...order.filter(key => filteredButtons.includes(key)),
|
||||
...MAIN_TOOLBAR_BUTTONS_PRIORITY.filter(key => !order.includes(key) && filteredButtons.includes(key)),
|
||||
...filteredButtons.filter(key => !order.includes(key) && !MAIN_TOOLBAR_BUTTONS_PRIORITY.includes(key))
|
||||
];
|
||||
|
||||
const mainButtonsKeys = mainToolbarButtonKeysOrder.slice(0, order.length);
|
||||
const overflowMenuButtons = filteredButtons.reduce((acc, key) => {
|
||||
if (!mainButtonsKeys.includes(key)) {
|
||||
acc.push(allButtons[key]);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, [] as IToolboxNativeButton[]);
|
||||
|
||||
// if we have 1 button in the overflow menu it is better to directly display it in the main toolbar by replacing
|
||||
// the "More" menu button with it.
|
||||
if (overflowMenuButtons.length === 1) {
|
||||
const button = overflowMenuButtons.shift()?.key;
|
||||
|
||||
button && mainButtonsKeys.push(button);
|
||||
}
|
||||
|
||||
const mainMenuButtons
|
||||
= mainButtonsKeys.map(key => allButtons[key]).sort((a, b) => {
|
||||
|
||||
// Native toolbox includes hangup and overflowmenu button keys, too
|
||||
// hangup goes last, overflowmenu goes second-to-last
|
||||
if (a.key === 'hangup' || a.key === 'overflowmenu') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.key === 'hangup' || b.key === 'overflowmenu') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0; // other buttons are sorted by priority
|
||||
});
|
||||
|
||||
return {
|
||||
mainMenuButtons,
|
||||
overflowMenuButtons
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { hasAvailableDevices } from '../base/devices/functions';
|
||||
import { hasAvailableDevices } from '../base/devices/functions.web';
|
||||
import { MEET_FEATURES } from '../base/jwt/constants';
|
||||
import { isJwtFeatureEnabled } from '../base/jwt/functions';
|
||||
import { IGUMPendingState } from '../base/media/types';
|
||||
@@ -7,7 +7,8 @@ import { isScreenMediaShared } from '../screen-share/functions';
|
||||
import { isWhiteboardVisible } from '../whiteboard/functions';
|
||||
|
||||
import { MAIN_TOOLBAR_BUTTONS_PRIORITY, TOOLBAR_TIMEOUT } from './constants';
|
||||
import { IMainToolbarButtonThresholds, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
|
||||
import { isButtonEnabled } from './functions.any';
|
||||
import { IGetVisibleButtonsParams, IToolboxButton, NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
@@ -22,19 +23,6 @@ export function getToolboxHeight() {
|
||||
return toolbox?.clientHeight || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified button is enabled.
|
||||
*
|
||||
* @param {string} buttonName - The name of the button. See {@link interfaceConfig}.
|
||||
* @param {Object|Array<string>} state - The redux state or the array with the enabled buttons.
|
||||
* @returns {boolean} - True if the button is enabled and false otherwise.
|
||||
*/
|
||||
export function isButtonEnabled(buttonName: string, state: IReduxState | Array<string>) {
|
||||
const buttons = Array.isArray(state) ? state : state['features/toolbox'].toolbarButtons || [];
|
||||
|
||||
return buttons.includes(buttonName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the toolbox is visible or not.
|
||||
*
|
||||
@@ -125,26 +113,6 @@ export function showOverflowDrawer(state: IReduxState) {
|
||||
return state['features/toolbox'].overflowDrawer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the overflow menu button is displayed and false otherwise.
|
||||
*
|
||||
* @param {IReduxState} state - The state from the Redux store.
|
||||
* @returns {boolean} - True if the overflow menu button is displayed and false otherwise.
|
||||
*/
|
||||
export function showOverflowMenu(state: IReduxState) {
|
||||
return state['features/toolbox'].overflowMenuVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the toolbox is enabled or not.
|
||||
*
|
||||
* @param {IReduxState} state - The state from the Redux store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isToolboxEnabled(state: IReduxState) {
|
||||
return state['features/toolbox'].enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the toolbar timeout from config or the default value.
|
||||
*
|
||||
@@ -176,15 +144,6 @@ function setButtonsNotifyClickMode(buttons: Object, buttonsWithNotifyClick: Map<
|
||||
});
|
||||
}
|
||||
|
||||
interface IGetVisibleButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxButton; };
|
||||
buttonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
|
||||
clientWidth: number;
|
||||
jwtDisabledButtons: string[];
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buttons that need to be rendered.
|
||||
*
|
||||
@@ -234,8 +193,10 @@ export function getVisibleButtons({
|
||||
button && mainButtonsKeys.push(button);
|
||||
}
|
||||
|
||||
const mainMenuButtons = mainButtonsKeys.map(key => allButtons[key]);
|
||||
|
||||
return {
|
||||
mainMenuButtons: mainButtonsKeys.map(key => allButtons[key]),
|
||||
mainMenuButtons,
|
||||
overflowMenuButtons
|
||||
};
|
||||
}
|
||||
|
||||
193
react/features/toolbox/hooks.native.ts
Normal file
193
react/features/toolbox/hooks.native.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import ChatButton from '../chat/components/native/ChatButton';
|
||||
import RaiseHandContainerButtons from '../reactions/components/native/RaiseHandContainerButtons';
|
||||
import TileViewButton from '../video-layout/components/TileViewButton';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import AudioMuteButton from './components/native/AudioMuteButton';
|
||||
import CustomOptionButton from './components/native/CustomOptionButton';
|
||||
import HangupContainerButtons from './components/native/HangupContainerButtons';
|
||||
import OverflowMenuButton from './components/native/OverflowMenuButton';
|
||||
import ScreenSharingButton from './components/native/ScreenSharingButton';
|
||||
import VideoMuteButton from './components/native/VideoMuteButton';
|
||||
import { isDesktopShareButtonDisabled } from './functions.native';
|
||||
import { ICustomToolbarButton, IToolboxNativeButton, NativeToolbarButton } from './types';
|
||||
|
||||
|
||||
const microphone = {
|
||||
key: 'microphone',
|
||||
Content: AudioMuteButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const camera = {
|
||||
key: 'camera',
|
||||
Content: VideoMuteButton,
|
||||
group: 0
|
||||
};
|
||||
|
||||
const chat = {
|
||||
key: 'chat',
|
||||
Content: ChatButton,
|
||||
group: 1
|
||||
};
|
||||
|
||||
const screensharing = {
|
||||
key: 'screensharing',
|
||||
Content: ScreenSharingButton,
|
||||
group: 1
|
||||
};
|
||||
|
||||
const raisehand = {
|
||||
key: 'raisehand',
|
||||
Content: RaiseHandContainerButtons,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const tileview = {
|
||||
key: 'tileview',
|
||||
Content: TileViewButton,
|
||||
group: 2
|
||||
};
|
||||
|
||||
const overflowmenu = {
|
||||
key: 'overflowmenu',
|
||||
Content: OverflowMenuButton,
|
||||
group: 3
|
||||
};
|
||||
|
||||
const hangup = {
|
||||
key: 'hangup',
|
||||
Content: HangupContainerButtons,
|
||||
group: 3
|
||||
};
|
||||
|
||||
/**
|
||||
* A hook that returns the audio mute button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getAudioMuteButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return microphone;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the video mute button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getVideoMuteButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return camera;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the chat button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getChatButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return chat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the screen sharing button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getScreenSharingButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
const _isScreenShareButtonDisabled = useSelector(isDesktopShareButtonDisabled);
|
||||
|
||||
if (!_isScreenShareButtonDisabled && !_iAmVisitor) {
|
||||
return screensharing;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the tile view button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getTileViewButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return tileview;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns the overflow menu button.
|
||||
*
|
||||
* @returns {Object | undefined}
|
||||
*/
|
||||
function getOverflowMenuButton() {
|
||||
const _iAmVisitor = useSelector(iAmVisitor);
|
||||
|
||||
if (!_iAmVisitor) {
|
||||
return overflowmenu;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all buttons that could be rendered.
|
||||
*
|
||||
* @param {Object} _customToolbarButtons - An array containing custom buttons objects.
|
||||
* @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
|
||||
*/
|
||||
export function useNativeToolboxButtons(
|
||||
_customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxNativeButton; } {
|
||||
const audioMuteButton = getAudioMuteButton();
|
||||
const videoMuteButton = getVideoMuteButton();
|
||||
const chatButton = getChatButton();
|
||||
const screenSharingButton = getScreenSharingButton();
|
||||
const tileViewButton = getTileViewButton();
|
||||
const overflowMenuButton = getOverflowMenuButton();
|
||||
|
||||
const buttons: { [key in NativeToolbarButton]?: IToolboxNativeButton; } = {
|
||||
microphone: audioMuteButton,
|
||||
camera: videoMuteButton,
|
||||
chat: chatButton,
|
||||
screensharing: screenSharingButton,
|
||||
raisehand,
|
||||
tileview: tileViewButton,
|
||||
overflowmenu: overflowMenuButton,
|
||||
hangup
|
||||
};
|
||||
const buttonKeys = Object.keys(buttons) as NativeToolbarButton[];
|
||||
|
||||
buttonKeys.forEach(
|
||||
key => typeof buttons[key] === 'undefined' && delete buttons[key]);
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
|
||||
prev[id] = {
|
||||
backgroundColor,
|
||||
key: id,
|
||||
id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
icon,
|
||||
text
|
||||
};
|
||||
|
||||
return prev;
|
||||
}, {} as { [key: string]: ICustomToolbarButton; });
|
||||
|
||||
return {
|
||||
...buttons,
|
||||
...customButtons
|
||||
};
|
||||
}
|
||||
@@ -270,7 +270,7 @@ function useHelpButton() {
|
||||
*/
|
||||
export function useToolboxButtons(
|
||||
_customToolbarButtons?: ICustomToolbarButton[]): { [key: string]: IToolboxButton; } {
|
||||
const dekstopSharing = getDesktopSharingButton();
|
||||
const desktopSharing = getDesktopSharingButton();
|
||||
const toggleCameraButton = useToggleCameraButton();
|
||||
const _fullscreen = getFullscreenButton();
|
||||
const security = useSecurityDialogButton();
|
||||
@@ -297,7 +297,7 @@ export function useToolboxButtons(
|
||||
microphone,
|
||||
camera,
|
||||
profile,
|
||||
desktop: dekstopSharing,
|
||||
desktop: desktopSharing,
|
||||
chat,
|
||||
raisehand,
|
||||
reactions,
|
||||
|
||||
46
react/features/toolbox/middleware.native.ts
Normal file
46
react/features/toolbox/middleware.native.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { I_AM_VISITOR_MODE } from '../visitors/actionTypes';
|
||||
|
||||
import { SET_TOOLBAR_BUTTONS } from './actionTypes';
|
||||
import { setMainToolbarThresholds } from './actions.native';
|
||||
import { NATIVE_THRESHOLDS, NATIVE_TOOLBAR_BUTTONS } from './constants';
|
||||
import { getToolbarButtons } from './functions.native';
|
||||
|
||||
|
||||
/**
|
||||
* Middleware which intercepts Toolbox actions to handle changes to the
|
||||
* visibility timeout of the Toolbox.
|
||||
*
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
|
||||
case UPDATE_CONFIG:
|
||||
case OVERWRITE_CONFIG:
|
||||
case I_AM_VISITOR_MODE:
|
||||
case SET_CONFIG: {
|
||||
const result = next(action);
|
||||
const { dispatch } = store;
|
||||
const state = store.getState();
|
||||
|
||||
const toolbarButtons = getToolbarButtons(state, NATIVE_TOOLBAR_BUTTONS);
|
||||
|
||||
if (action.type !== I_AM_VISITOR_MODE) {
|
||||
dispatch(setMainToolbarThresholds(NATIVE_THRESHOLDS));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: SET_TOOLBAR_BUTTONS,
|
||||
toolbarButtons
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -1,12 +1,10 @@
|
||||
import { batch } from 'react-redux';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { OVERWRITE_CONFIG, SET_CONFIG, UPDATE_CONFIG } from '../base/config/actionTypes';
|
||||
import { NotifyClickButton } from '../base/config/configType';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { I_AM_VISITOR_MODE } from '../visitors/actionTypes';
|
||||
import { iAmVisitor } from '../visitors/functions';
|
||||
|
||||
import {
|
||||
CLEAR_TOOLBOX_TIMEOUT,
|
||||
@@ -17,7 +15,8 @@ import {
|
||||
SET_TOOLBOX_TIMEOUT
|
||||
} from './actionTypes';
|
||||
import { setMainToolbarThresholds } from './actions.web';
|
||||
import { TOOLBAR_BUTTONS, VISITORS_MODE_BUTTONS } from './constants';
|
||||
import { THRESHOLDS, TOOLBAR_BUTTONS } from './constants';
|
||||
import { getToolbarButtons } from './functions.web';
|
||||
import { NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
import './subscriber.web';
|
||||
@@ -55,7 +54,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
batch(() => {
|
||||
if (action.type !== I_AM_VISITOR_MODE) {
|
||||
dispatch(setMainToolbarThresholds());
|
||||
dispatch(setMainToolbarThresholds(THRESHOLDS));
|
||||
}
|
||||
dispatch({
|
||||
type: SET_BUTTONS_WITH_NOTIFY_CLICK,
|
||||
@@ -69,7 +68,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
});
|
||||
}
|
||||
|
||||
const toolbarButtons = _getToolbarButtons(state);
|
||||
const toolbarButtons = getToolbarButtons(state, TOOLBAR_BUTTONS);
|
||||
|
||||
dispatch({
|
||||
type: SET_TOOLBAR_BUTTONS,
|
||||
@@ -171,25 +170,3 @@ function _buildButtonsArray(
|
||||
|
||||
return new Map([ ...customButtonsWithNotifyClick, ...buttons ]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled toolbar buttons.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
function _getToolbarButtons(state: IReduxState): Array<string> {
|
||||
const { toolbarButtons, customToolbarButtons } = state['features/base/config'];
|
||||
const customButtons = customToolbarButtons?.map(({ id }) => id);
|
||||
let buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
|
||||
if (iAmVisitor(state)) {
|
||||
buttons = VISITORS_MODE_BUTTONS.filter(button => buttons.indexOf(button) > -1);
|
||||
}
|
||||
|
||||
if (customButtons) {
|
||||
return [ ...buttons, ...customButtons ];
|
||||
}
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
import { THRESHOLDS } from './constants';
|
||||
import { NATIVE_THRESHOLDS, THRESHOLDS } from './constants';
|
||||
import { IMainToolbarButtonThresholds, NOTIFY_CLICK_MODE } from './types';
|
||||
|
||||
/**
|
||||
@@ -52,7 +52,7 @@ const INITIAL_STATE = {
|
||||
/**
|
||||
* The thresholds for screen size and visible main toolbar buttons.
|
||||
*/
|
||||
mainToolbarButtonsThresholds: THRESHOLDS,
|
||||
mainToolbarButtonsThresholds: navigator.product === 'ReactNative' ? NATIVE_THRESHOLDS : THRESHOLDS,
|
||||
|
||||
participantMenuButtonsWithNotifyClick: new Map(),
|
||||
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { ComponentType } from 'react';
|
||||
|
||||
import { CustomOptionButton } from './components';
|
||||
|
||||
export interface IToolboxButton {
|
||||
Content: ComponentType<any>;
|
||||
group: number;
|
||||
key: string;
|
||||
}
|
||||
|
||||
export interface IToolboxNativeButton {
|
||||
Content: ComponentType<any>;
|
||||
backgroundColor?: string;
|
||||
group: number;
|
||||
icon?: string;
|
||||
id?: string;
|
||||
key: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export type ToolbarButton = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
@@ -28,6 +36,7 @@ export type ToolbarButton = 'camera' |
|
||||
'mute-everyone' |
|
||||
'mute-video-everyone' |
|
||||
'noisesuppression' |
|
||||
'overflowmenu' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
@@ -52,12 +61,12 @@ export enum NOTIFY_CLICK_MODE {
|
||||
}
|
||||
|
||||
export type IMainToolbarButtonThresholds = Array<{
|
||||
order: Array<ToolbarButton | string>;
|
||||
order: Array<ToolbarButton | NativeToolbarButton | string>;
|
||||
width: number;
|
||||
}>;
|
||||
|
||||
export interface ICustomToolbarButton {
|
||||
Content?: typeof CustomOptionButton;
|
||||
Content?: ComponentType<any>;
|
||||
backgroundColor?: string;
|
||||
group?: number;
|
||||
icon: string;
|
||||
@@ -65,3 +74,28 @@ export interface ICustomToolbarButton {
|
||||
key?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export type NativeToolbarButton = 'camera' |
|
||||
'chat' |
|
||||
'microphone' |
|
||||
'raisehand' |
|
||||
'screensharing' |
|
||||
'tileview' |
|
||||
'overflowmenu' |
|
||||
'hangup';
|
||||
|
||||
export interface IGetVisibleNativeButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxNativeButton; };
|
||||
clientWidth: number;
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
export interface IGetVisibleButtonsParams {
|
||||
allButtons: { [key: string]: IToolboxButton; };
|
||||
buttonsWithNotifyClick: Map<string, NOTIFY_CLICK_MODE>;
|
||||
clientWidth: number;
|
||||
jwtDisabledButtons: string[];
|
||||
mainToolbarButtonsThresholds: IMainToolbarButtonThresholds;
|
||||
toolbarButtons: string[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
|
||||
import { TRANSCRIBER_LEFT } from './actionTypes';
|
||||
import './subscriber';
|
||||
@@ -17,7 +16,7 @@ MiddlewareRegistry.register(({ dispatch }) => next => action => {
|
||||
if (action.abruptly) {
|
||||
dispatch(showErrorNotification({
|
||||
titleKey: 'transcribing.failed'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ class GrantModeratorDialog extends AbstractGrantModeratorDialog {
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
_onSubmit: () => boolean;
|
||||
}
|
||||
|
||||
export default translate(connect(abstractMapStateToProps)(GrantModeratorDialog));
|
||||
|
||||
@@ -105,7 +105,7 @@ function _inviteRooms(rooms: ISipRoom[], conference: IJitsiConference, dispatch:
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey: 'videoSIPGW.errorInvite',
|
||||
titleKey: 'videoSIPGW.errorInviteTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -159,14 +159,14 @@ function _sessionStateChanged(
|
||||
displayName: event.displayName
|
||||
},
|
||||
descriptionKey: 'videoSIPGW.errorInviteFailed'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
|
||||
});
|
||||
}
|
||||
case JitsiSIPVideoGWStatus.STATE_OFF: {
|
||||
if (event.failureReason === JitsiSIPVideoGWStatus.STATUS_BUSY) {
|
||||
return showErrorNotification({
|
||||
descriptionKey: 'videoSIPGW.busy',
|
||||
titleKey: 'videoSIPGW.busyTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG);
|
||||
});
|
||||
} else if (event.failureReason) {
|
||||
logger.error(`Unknown sip videogw error ${event.newState} ${
|
||||
event.failureReason}`);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
local jid = require 'util.jid';
|
||||
local json = require 'cjson.safe';
|
||||
local queue = require "util.queue";
|
||||
local uuid_gen = require "util.uuid".generate;
|
||||
local main_util = module:require "util";
|
||||
local ends_with = main_util.ends_with;
|
||||
local get_room_from_jid = main_util.get_room_from_jid;
|
||||
local is_healthcheck_room = main_util.is_healthcheck_room;
|
||||
local internal_room_jid_match_rewrite = main_util.internal_room_jid_match_rewrite;
|
||||
local presence_check_status = main_util.presence_check_status;
|
||||
@@ -13,9 +16,15 @@ end
|
||||
|
||||
local QUEUE_MAX_SIZE = 500;
|
||||
|
||||
-- Module that generates a unique meetingId, attaches it to the room
|
||||
-- and adds it to all disco info form data (when room is queried or in the
|
||||
-- initial room owner config)
|
||||
-- Common module for all logic that can be loaded under the conference muc component.
|
||||
--
|
||||
-- This module:
|
||||
-- a) Generates a unique meetingId, attaches it to the room and adds it to all disco info form data
|
||||
-- (when room is queried or in the initial room owner config).
|
||||
-- b) Updates user region (obtain it from the incoming http headers) in the occupant's presence on pre-join.
|
||||
-- c) Avoids any participant joining the room in the interval between creating the room and jicofo entering the room.
|
||||
-- d) Removes any nick that maybe set to messages being sent to the room.
|
||||
-- e) Fires event for received endpoint messages (optimization to decode them once).
|
||||
|
||||
-- Hook to assign meetingId for new rooms
|
||||
module:hook("muc-room-created", function(event)
|
||||
@@ -149,3 +158,82 @@ module:hook('jicofo-unlock-room', handle_jicofo_unlock);
|
||||
module:hook("muc-occupant-groupchat", function(event)
|
||||
event.stanza:remove_children('nick', 'http://jabber.org/protocol/nick');
|
||||
end, 45); -- prosody check is prio 50, we want to run after it
|
||||
|
||||
module:hook('message/bare', function(event)
|
||||
local stanza = event.stanza;
|
||||
|
||||
if stanza.attr.type ~= 'groupchat' then
|
||||
return nil;
|
||||
end
|
||||
|
||||
-- we are interested in all messages without a body
|
||||
local body = stanza:get_child('body')
|
||||
if body then
|
||||
return;
|
||||
end
|
||||
|
||||
local room = get_room_from_jid(stanza.attr.to);
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s', stanza.attr.to);
|
||||
return;
|
||||
end
|
||||
|
||||
local occupant_jid = stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant sending msg %s was not found in room %s", occupant_jid, room.jid)
|
||||
return;
|
||||
end
|
||||
|
||||
local json_message = stanza:get_child_text('json-message', 'http://jitsi.org/jitmeet');
|
||||
if not json_message then
|
||||
return;
|
||||
end
|
||||
|
||||
-- TODO: add optimization by moving type and certain fields like is_interim as attribute on 'json-message'
|
||||
-- using string find is roughly 70x faster than json decode for checking the value
|
||||
if string.find(json_message, '"is_interim":true', 1, true) then
|
||||
return;
|
||||
end
|
||||
|
||||
local msg_obj, error = json.decode(json_message);
|
||||
|
||||
if error then
|
||||
module:log('error', 'Error decoding data error:%s Sender: %s to:%s', error, stanza.attr.from, stanza.attr.to);
|
||||
return true;
|
||||
end
|
||||
|
||||
if msg_obj.transcript ~= nil then
|
||||
local transcription = msg_obj;
|
||||
|
||||
-- in case of the string matching optimization above failed
|
||||
if transcription.is_interim then
|
||||
return;
|
||||
end
|
||||
|
||||
-- TODO what if we have multiple alternative transcriptions not just 1
|
||||
local text_message = transcription.transcript[1].text;
|
||||
--do not send empty messages
|
||||
if text_message == '' then
|
||||
return;
|
||||
end
|
||||
|
||||
local user_id = transcription.participant.id;
|
||||
local who = room:get_occupant_by_nick(jid.bare(room.jid)..'/'..user_id);
|
||||
|
||||
transcription.jid = who and who.jid;
|
||||
transcription.session_id = room._data.meetingId;
|
||||
|
||||
local tenant, conference_name, id = extract_subdomain(jid.node(room.jid));
|
||||
transcription.fqn = tenant..'/'..conference_name;
|
||||
transcription.customer_id = id;
|
||||
|
||||
return module:fire_event('jitsi-transcript-received', {
|
||||
room = room, occupant = occupant, transcription = transcription, stanza = stanza });
|
||||
end
|
||||
|
||||
return module:fire_event('jitsi-endpoint-message-received', {
|
||||
room = room, occupant = occupant, message = msg_obj,
|
||||
origin = event.origin,
|
||||
stanza = stanza, raw_message = json_message });
|
||||
end);
|
||||
|
||||
@@ -14,31 +14,6 @@ local is_healthcheck_room = util.is_healthcheck_room;
|
||||
local POLLS_LIMIT = 128;
|
||||
local POLL_PAYLOAD_LIMIT = 1024;
|
||||
|
||||
-- Checks if the given stanza contains a JSON message,
|
||||
-- and that the message type pertains to the polls feature.
|
||||
-- If yes, returns the parsed message. Otherwise, returns nil.
|
||||
local function get_poll_message(stanza)
|
||||
if stanza.attr.type ~= "groupchat" then
|
||||
return nil;
|
||||
end
|
||||
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
|
||||
if json_data == nil then
|
||||
return nil;
|
||||
end
|
||||
if string.len(json_data) >= POLL_PAYLOAD_LIMIT then
|
||||
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
|
||||
return nil;
|
||||
end
|
||||
local data, error = json.decode(json_data);
|
||||
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
|
||||
if error then
|
||||
module:log('error', 'Error decoding data error:%s', error);
|
||||
end
|
||||
return nil;
|
||||
end
|
||||
return data;
|
||||
end
|
||||
|
||||
-- Logs a warning and returns true if a room does not
|
||||
-- have poll data associated with it.
|
||||
local function check_polls(room)
|
||||
@@ -87,21 +62,22 @@ end);
|
||||
-- by listening to "new-poll" and "answer-poll" messages,
|
||||
-- and updating the room poll data accordingly.
|
||||
-- This mirrors the client-side poll update logic.
|
||||
module:hook("message/bare", function(event)
|
||||
local data = get_poll_message(event.stanza);
|
||||
if data == nil then return end
|
||||
module:hook('jitsi-endpoint-message-received', function(event)
|
||||
local data, error, occupant, room, origin, stanza
|
||||
= event.message, event.error, event.occupant, event.room, event.origin, event.stanza;
|
||||
|
||||
local room = muc.get_room_from_jid(event.stanza.attr.to);
|
||||
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
|
||||
return;
|
||||
end
|
||||
|
||||
if string.len(event.raw_message) >= POLL_PAYLOAD_LIMIT then
|
||||
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
|
||||
return nil;
|
||||
end
|
||||
|
||||
if data.type == "new-poll" then
|
||||
if check_polls(room) then return end
|
||||
|
||||
local occupant_jid = event.stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
|
||||
return
|
||||
end
|
||||
local poll_creator = get_occupant_details(occupant)
|
||||
if not poll_creator then
|
||||
module:log("error", "Cannot retrieve poll creator id and name for %s from %s", occupant.jid, room.jid)
|
||||
@@ -115,7 +91,7 @@ module:hook("message/bare", function(event)
|
||||
|
||||
if room.polls.by_id[data.pollId] ~= nil then
|
||||
module:log("error", "Poll already exists: %s", data.pollId);
|
||||
event.origin.send(st.error_reply(event.stanza, 'cancel', 'not-allowed', 'Poll already exists'));
|
||||
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll already exists'));
|
||||
return true;
|
||||
end
|
||||
|
||||
@@ -150,16 +126,9 @@ module:hook("message/bare", function(event)
|
||||
}
|
||||
}
|
||||
module:fire_event("poll-created", pollData);
|
||||
|
||||
elseif data.type == "answer-poll" then
|
||||
if check_polls(room) then return end
|
||||
|
||||
local occupant_jid = event.stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant %s does not exists for room %s", occupant_jid, room.jid)
|
||||
return
|
||||
end
|
||||
local poll = room.polls.by_id[data.pollId];
|
||||
if poll == nil then
|
||||
module:log("warn", "answering inexistent poll");
|
||||
|
||||
@@ -5,16 +5,13 @@ local update_presence_identity = module:require "util".update_presence_identity;
|
||||
-- values are set in the session, then insert them into the presence messages
|
||||
-- for that session.
|
||||
function on_message(event)
|
||||
if event and event["stanza"] then
|
||||
if event.origin and event.origin.jitsi_meet_context_user then
|
||||
|
||||
local stanza, session = event.stanza, event.origin;
|
||||
if stanza and session then
|
||||
update_presence_identity(
|
||||
event.stanza,
|
||||
event.origin.jitsi_meet_context_user,
|
||||
event.origin.jitsi_meet_context_group
|
||||
stanza,
|
||||
session.jitsi_meet_context_user,
|
||||
session.jitsi_meet_context_group
|
||||
);
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -535,17 +535,11 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
|
||||
end
|
||||
end
|
||||
end);
|
||||
host_module:hook("message/bare", function(event)
|
||||
local stanza = event.stanza;
|
||||
|
||||
if stanza.attr.type ~= "groupchat" then
|
||||
return;
|
||||
end
|
||||
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
|
||||
if json_data == nil then
|
||||
return;
|
||||
end
|
||||
local data, error = json.decode(json_data);
|
||||
host_module:hook('jitsi-endpoint-message-received', function(event)
|
||||
local data, error, occupant, room, stanza
|
||||
= event.message, event.error, event.occupant, event.room, event.stanza;
|
||||
|
||||
if not data or data.type ~= 'visitors'
|
||||
or (data.action ~= "promotion-response" and data.action ~= "demote-request") then
|
||||
if error then
|
||||
@@ -554,17 +548,9 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
|
||||
return;
|
||||
end
|
||||
|
||||
local room = get_room_from_jid(event.stanza.attr.to);
|
||||
|
||||
local occupant_jid = event.stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(occupant_jid);
|
||||
if not occupant then
|
||||
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
|
||||
return
|
||||
end
|
||||
if occupant.role ~= 'moderator' then
|
||||
module:log('error', 'Occupant %s sending response message but not moderator in room %s',
|
||||
occupant_jid, room.jid);
|
||||
occupant.jid, room.jid);
|
||||
return false;
|
||||
end
|
||||
|
||||
@@ -590,7 +576,6 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
else
|
||||
if data.id then
|
||||
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
|
||||
@@ -604,6 +589,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
|
||||
|
||||
return true; -- halt processing, but return true that we handled it
|
||||
end);
|
||||
|
||||
if visitors_queue_service then
|
||||
host_module:hook('muc-room-created', function (event)
|
||||
local room = event.room;
|
||||
|
||||
@@ -5,6 +5,7 @@ local timer = require "util.timer";
|
||||
local http = require "net.http";
|
||||
local cache = require "util.cache";
|
||||
local array = require "util.array";
|
||||
local is_set = require 'util.set'.is_set;
|
||||
|
||||
local http_timeout = 30;
|
||||
local have_async, async = pcall(require, "util.async");
|
||||
@@ -32,6 +33,7 @@ local roomless_iqs = {};
|
||||
local OUTBOUND_SIP_JIBRI_PREFIXES = { 'outbound-sip-jibri@', 'sipjibriouta@', 'sipjibrioutb@' };
|
||||
local INBOUND_SIP_JIBRI_PREFIXES = { 'inbound-sip-jibri@', 'sipjibriina@', 'sipjibriina@' };
|
||||
local RECORDER_PREFIXES = module:get_option_inherited_set('recorder_prefixes', { 'recorder@recorder.', 'jibria@recorder.', 'jibrib@recorder.' });
|
||||
local TRANSCRIBER_PREFIXES = module:get_option_inherited_set('transcriber_prefixes', { 'transcriber@recorder.', 'transcribera@recorder.', 'transcriberb@recorder.' });
|
||||
|
||||
local split_subdomain_cache = cache.new(1000);
|
||||
local extract_subdomain_cache = cache.new(1000);
|
||||
@@ -206,8 +208,7 @@ end
|
||||
-- @param creator_group the group of the user who created the user which
|
||||
-- presence we are updating (this is the poltergeist case, where a user creates
|
||||
-- a poltergeist), optional.
|
||||
function update_presence_identity(
|
||||
stanza, user, group, creator_user, creator_group)
|
||||
function update_presence_identity(stanza, user, group, creator_user, creator_group)
|
||||
|
||||
-- First remove any 'identity' element if it already
|
||||
-- exists, so it cannot be spoofed by a client
|
||||
@@ -220,7 +221,11 @@ function update_presence_identity(
|
||||
end
|
||||
return tag
|
||||
end
|
||||
)
|
||||
);
|
||||
|
||||
if not user then
|
||||
return;
|
||||
end
|
||||
|
||||
stanza:tag("identity"):tag("user");
|
||||
for k, v in pairs(user) do
|
||||
@@ -267,17 +272,18 @@ function is_feature_allowed(ft, features, granted_features, is_moderator)
|
||||
end
|
||||
|
||||
--- Extracts the subdomain and room name from internal jid node [foo]room1
|
||||
-- @return subdomain(optional, if extracted or nil), the room name
|
||||
-- @return subdomain(optional, if extracted or nil), the room name, the customer_id in case of vpaas
|
||||
function extract_subdomain(room_node)
|
||||
local ret = extract_subdomain_cache:get(room_node);
|
||||
if ret then
|
||||
return ret.subdomain, ret.room;
|
||||
return ret.subdomain, ret.room, ret.customer_id;
|
||||
end
|
||||
|
||||
local subdomain, room_name = room_node:match("^%[([^%]]+)%](.+)$");
|
||||
local cache_value = {subdomain=subdomain, room=room_name};
|
||||
local _, customer_id = subdomain and subdomain:match("^(vpaas%-magic%-cookie%-)(.*)$") or nil, nil;
|
||||
local cache_value = { subdomain=subdomain, room=room_name, customer_id=customer_id };
|
||||
extract_subdomain_cache:set(room_node, cache_value);
|
||||
return subdomain, room_name;
|
||||
return subdomain, room_name, customer_id;
|
||||
end
|
||||
|
||||
function starts_with(str, start)
|
||||
@@ -288,14 +294,25 @@ function starts_with(str, start)
|
||||
end
|
||||
|
||||
function starts_with_one_of(str, prefixes)
|
||||
if not str then
|
||||
if not str or not prefixes then
|
||||
return false;
|
||||
end
|
||||
for i=1,#prefixes do
|
||||
if starts_with(str, prefixes[i]) then
|
||||
return prefixes[i];
|
||||
|
||||
if is_set(prefixes) then
|
||||
-- set is a table with keys and value of true
|
||||
for k, _ in prefixes:items() do
|
||||
if starts_with(str, k) then
|
||||
return k;
|
||||
end
|
||||
end
|
||||
else
|
||||
for _, v in pairs(prefixes) do
|
||||
if starts_with(str, v) then
|
||||
return v;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -481,6 +498,7 @@ function is_sip_jigasi(stanza)
|
||||
return stanza:get_child('initiator', 'http://jitsi.org/protocol/jigasi');
|
||||
end
|
||||
|
||||
-- This requires presence stanza being passed
|
||||
function is_transcriber_jigasi(stanza)
|
||||
if not stanza then
|
||||
return false;
|
||||
@@ -501,6 +519,9 @@ function is_transcriber_jigasi(stanza)
|
||||
return false;
|
||||
end
|
||||
|
||||
function is_transcriber(jid)
|
||||
return starts_with_one_of(jid, TRANSCRIBER_PREFIXES);
|
||||
end
|
||||
|
||||
function get_sip_jibri_email_prefix(email)
|
||||
if not email then
|
||||
@@ -616,6 +637,7 @@ return {
|
||||
is_moderated = is_moderated;
|
||||
is_sip_jibri_join = is_sip_jibri_join;
|
||||
is_sip_jigasi = is_sip_jigasi;
|
||||
is_transcriber = is_transcriber;
|
||||
is_transcriber_jigasi = is_transcriber_jigasi;
|
||||
is_vpaas = is_vpaas;
|
||||
get_focus_occupant = get_focus_occupant;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* global APP $ */
|
||||
|
||||
import { multiremotebrowser } from '@wdio/globals';
|
||||
import assert from 'assert';
|
||||
import { Key } from 'webdriverio';
|
||||
|
||||
import { IConfig } from '../../react/features/base/config/configType';
|
||||
@@ -10,9 +11,11 @@ import ChatPanel from '../pageobjects/ChatPanel';
|
||||
import Filmstrip from '../pageobjects/Filmstrip';
|
||||
import IframeAPI from '../pageobjects/IframeAPI';
|
||||
import InviteDialog from '../pageobjects/InviteDialog';
|
||||
import LargeVideo from '../pageobjects/LargeVideo';
|
||||
import LobbyScreen from '../pageobjects/LobbyScreen';
|
||||
import Notifications from '../pageobjects/Notifications';
|
||||
import ParticipantsPane from '../pageobjects/ParticipantsPane';
|
||||
import PasswordDialog from '../pageobjects/PasswordDialog';
|
||||
import PreJoinScreen from '../pageobjects/PreJoinScreen';
|
||||
import SecurityDialog from '../pageobjects/SecurityDialog';
|
||||
import SettingsDialog from '../pageobjects/SettingsDialog';
|
||||
@@ -27,6 +30,13 @@ export const P2_DISPLAY_NAME = 'p2';
|
||||
export const P3_DISPLAY_NAME = 'p3';
|
||||
export const P4_DISPLAY_NAME = 'p4';
|
||||
|
||||
interface IWaitForSendReceiveDataOptions {
|
||||
checkReceive?: boolean;
|
||||
checkSend?: boolean;
|
||||
msg?: string;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Participant.
|
||||
*/
|
||||
@@ -83,6 +93,24 @@ export class Participant {
|
||||
this._jwt = jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for <tt>this.driver.execute</tt> that would catch errors, print them and throw them again.
|
||||
*
|
||||
* @param {string | ((...innerArgs: InnerArguments) => ReturnValue)} script - The script that will be executed.
|
||||
* @param {any[]} args - The rest of the arguments.
|
||||
* @returns {ReturnValue} - The result of the script.
|
||||
*/
|
||||
async execute<ReturnValue, InnerArguments extends any[]>(
|
||||
script: string | ((...innerArgs: InnerArguments) => ReturnValue),
|
||||
...args: InnerArguments): Promise<ReturnValue> {
|
||||
try {
|
||||
return await this.driver.execute(script, ...args);
|
||||
} catch (error) {
|
||||
console.error('An error occured while trying to execute a script: ', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns participant endpoint ID.
|
||||
*
|
||||
@@ -90,8 +118,8 @@ export class Participant {
|
||||
*/
|
||||
async getEndpointId(): Promise<string> {
|
||||
if (!this._endpointId) {
|
||||
this._endpointId = await this.driver.execute(() => { // eslint-disable-line arrow-body-style
|
||||
return APP.conference.getMyUserId();
|
||||
this._endpointId = await this.execute(() => { // eslint-disable-line arrow-body-style
|
||||
return APP?.conference?.getMyUserId();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -208,8 +236,8 @@ export class Participant {
|
||||
|
||||
const parallel = [];
|
||||
|
||||
parallel.push(driver.execute((name, sessionId, prefix) => {
|
||||
APP.UI.dockToolbar(true);
|
||||
parallel.push(this.execute((name, sessionId, prefix) => {
|
||||
APP?.UI?.dockToolbar(true);
|
||||
|
||||
// disable keyframe animations (.fadeIn and .fadeOut classes)
|
||||
$('<style>.notransition * { '
|
||||
@@ -252,7 +280,7 @@ export class Participant {
|
||||
*/
|
||||
async waitForPageToLoad(): Promise<void> {
|
||||
return this.driver.waitUntil(
|
||||
() => this.driver.execute(() => document.readyState === 'complete'),
|
||||
() => this.execute(() => document.readyState === 'complete'),
|
||||
{
|
||||
timeout: 30_000, // 30 seconds
|
||||
timeoutMsg: `Timeout waiting for Page Load Request to complete for ${this.name}.`
|
||||
@@ -274,15 +302,15 @@ export class Participant {
|
||||
/**
|
||||
* Checks if the participant is in the meeting.
|
||||
*/
|
||||
async isInMuc() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
|
||||
isInMuc() {
|
||||
return this.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the participant is a moderator in the meeting.
|
||||
*/
|
||||
async isModerator() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined'
|
||||
return await this.execute(() => typeof APP !== 'undefined'
|
||||
&& APP.store?.getState()['features/base/participants']?.local?.role === 'moderator');
|
||||
}
|
||||
|
||||
@@ -290,7 +318,7 @@ export class Participant {
|
||||
* Checks if the meeting supports breakout rooms.
|
||||
*/
|
||||
async isBreakoutRoomsSupported() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined'
|
||||
return await this.execute(() => typeof APP !== 'undefined'
|
||||
&& APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isSupported());
|
||||
}
|
||||
|
||||
@@ -298,7 +326,7 @@ export class Participant {
|
||||
* Checks if the participant is in breakout room.
|
||||
*/
|
||||
async isInBreakoutRoom() {
|
||||
return await this.driver.execute(() => typeof APP !== 'undefined'
|
||||
return await this.execute(() => typeof APP !== 'undefined'
|
||||
&& APP.store?.getState()['features/base/conference'].conference?.getBreakoutRooms()?.isBreakoutRoom());
|
||||
}
|
||||
|
||||
@@ -322,60 +350,59 @@ export class Participant {
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async waitForIceConnected(): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(() =>
|
||||
driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
|
||||
waitForIceConnected(): Promise<void> {
|
||||
return this.driver.waitUntil(() =>
|
||||
this.execute(() => APP?.conference?.getConnectionState() === 'connected'), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected ICE to be connected for 15s for ${this.name}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for send and receive data.
|
||||
* Waits for ICE to get connected on the p2p connection.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async waitForSendReceiveData(
|
||||
timeout = 15_000, msg = `expected to receive/send data in 15s for ${this.name}`): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(() => driver.execute(() => {
|
||||
const stats = APP.conference.getStats();
|
||||
const bitrateMap = stats?.bitrate || {};
|
||||
const rtpStats = {
|
||||
uploadBitrate: bitrateMap.upload || 0,
|
||||
downloadBitrate: bitrateMap.download || 0
|
||||
};
|
||||
|
||||
return rtpStats.uploadBitrate > 0 && rtpStats.downloadBitrate > 0;
|
||||
}), {
|
||||
timeout,
|
||||
timeoutMsg: msg
|
||||
waitForP2PIceConnected(): Promise<void> {
|
||||
return this.driver.waitUntil(() =>
|
||||
this.execute(() => APP?.conference?.getP2PConnectionState() === 'connected'), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected P2P ICE to be connected for 15s for ${this.name}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for send and receive data.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {boolean} options.checkSend - If true we will chec
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async waitForSendData(
|
||||
timeout = 15_000, msg = `expected to send data in 15s for ${this.name}`): Promise<void> {
|
||||
const driver = this.driver;
|
||||
waitForSendReceiveData({
|
||||
checkSend = true,
|
||||
checkReceive = true,
|
||||
timeout = 15_000,
|
||||
msg
|
||||
} = {} as IWaitForSendReceiveDataOptions): Promise<void> {
|
||||
if (!checkSend && !checkReceive) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return driver.waitUntil(() => driver.execute(() => {
|
||||
const stats = APP.conference.getStats();
|
||||
const lMsg = msg ?? `expected to ${
|
||||
checkSend && checkReceive ? 'receive/send' : checkSend ? 'send' : 'receive'} data in 15s for ${this.name}`;
|
||||
|
||||
return this.driver.waitUntil(() => this.execute((pCheckSend: boolean, pCheckReceive: boolean) => {
|
||||
const stats = APP?.conference?.getStats();
|
||||
const bitrateMap = stats?.bitrate || {};
|
||||
const rtpStats = {
|
||||
uploadBitrate: bitrateMap.upload || 0
|
||||
uploadBitrate: bitrateMap.upload || 0,
|
||||
downloadBitrate: bitrateMap.download || 0
|
||||
};
|
||||
|
||||
return rtpStats.uploadBitrate > 0;
|
||||
}), {
|
||||
return (rtpStats.uploadBitrate > 0 || !pCheckSend) && (rtpStats.downloadBitrate > 0 || !pCheckReceive);
|
||||
}, checkSend, checkReceive), {
|
||||
timeout,
|
||||
timeoutMsg: msg
|
||||
timeoutMsg: lMsg
|
||||
});
|
||||
}
|
||||
|
||||
@@ -385,11 +412,11 @@ export class Participant {
|
||||
* @param {number} number - The number of remote streams to wait for.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
waitForRemoteStreams(number: number): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(() =>
|
||||
driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
|
||||
async waitForRemoteStreams(number: number): Promise<void> {
|
||||
return await this.driver.waitUntil(async () => await this.execute(
|
||||
count => (APP?.conference?.getNumberOfParticipantsWithTracks() ?? -1) >= count,
|
||||
number
|
||||
), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected number of remote streams:${number} in 15s for ${this.name}`
|
||||
});
|
||||
@@ -403,12 +430,12 @@ export class Participant {
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
waitForParticipants(number: number, msg?: string): Promise<void> {
|
||||
const driver = this.driver;
|
||||
|
||||
return driver.waitUntil(() => driver.execute(count => APP.conference.listMembers().length === count, number), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: msg || `not the expected participants ${number} in 15s for ${this.name}`
|
||||
});
|
||||
return this.driver.waitUntil(
|
||||
() => this.execute(count => (APP?.conference?.listMembers()?.length ?? -1) === count, number),
|
||||
{
|
||||
timeout: 15_000,
|
||||
timeoutMsg: msg || `not the expected participants ${number} in 15s for ${this.name}`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,6 +497,15 @@ export class Participant {
|
||||
return new ParticipantsPane(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the large video page object.
|
||||
*
|
||||
* @returns {LargeVideo}
|
||||
*/
|
||||
getLargeVideo(): LargeVideo {
|
||||
return new LargeVideo(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the videoQuality Dialog.
|
||||
*
|
||||
@@ -497,6 +533,13 @@ export class Participant {
|
||||
return new SettingsDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the password dialog.
|
||||
*/
|
||||
getPasswordDialog(): PasswordDialog {
|
||||
return new PasswordDialog(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the prejoin screen.
|
||||
*/
|
||||
@@ -546,7 +589,7 @@ export class Participant {
|
||||
}
|
||||
|
||||
// do a hangup, to make sure unavailable presence is sent
|
||||
await this.driver.execute(() => typeof APP !== 'undefined' && APP?.conference?.hangup());
|
||||
await this.execute(() => typeof APP !== 'undefined' && APP.conference?.hangup());
|
||||
|
||||
// let's give it some time to leave the muc, we redirect after hangup so we should wait for the
|
||||
// change of url
|
||||
@@ -608,29 +651,6 @@ export class Participant {
|
||||
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets avatar SRC attribute for the one displayed on large video.
|
||||
*/
|
||||
async getLargeVideoAvatar() {
|
||||
const avatar = this.driver.$('//img[@id="dominantSpeakerAvatar"]');
|
||||
|
||||
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns resource part of the JID of the user who is currently displayed in the large video area.
|
||||
*/
|
||||
async getLargeVideoResource() {
|
||||
return await this.driver.execute(() => APP.UI.getLargeVideoID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source of the large video currently shown.
|
||||
*/
|
||||
async getLargeVideoId() {
|
||||
return this.driver.execute('return document.getElementById("largeVideo").srcObject.id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
|
||||
* There are 3 options for avatar:
|
||||
@@ -700,29 +720,136 @@ export class Participant {
|
||||
return this.driver.$('div[data-testid="dialog.leaveReason"]').isDisplayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the audio level for a participant.
|
||||
*
|
||||
* @param observer
|
||||
* @param participant
|
||||
* @return
|
||||
*/
|
||||
async getRemoteAudioLevel(p: Participant) {
|
||||
const jid = await p.getEndpointId();
|
||||
|
||||
return await this.execute(id => {
|
||||
const level = APP?.conference?.getPeerSSRCAudioLevel(id);
|
||||
|
||||
return level ? level.toFixed(2) : null;
|
||||
}, jid);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the participant to have his audio muted/unmuted from given observer's
|
||||
* perspective. The method will fail the test if something goes wrong or
|
||||
* the audio muted status is different than the expected one. We wait up to
|
||||
* 3 seconds for the expected status to appear.
|
||||
*
|
||||
* @param testee - instance of the participant for whom we're checking the audio muted status.
|
||||
* @param muted - <tt>true</tt> to wait for audio muted status or <tt>false</tt> to wait for the participant to
|
||||
* unmute.
|
||||
*/
|
||||
async waitForAudioMuted(testee: Participant, muted: boolean): Promise<void> {
|
||||
// Waits for the correct icon
|
||||
await this.getFilmstrip().assertAudioMuteIconIsDisplayed(testee, !muted);
|
||||
|
||||
// Extended timeout for 'unmuted' to make tests more resilient to
|
||||
// unexpected glitches.
|
||||
const timeout = muted ? 3_000 : 6_000;
|
||||
|
||||
// Give it 3 seconds to not get any audio or to receive some
|
||||
// depending on "muted" argument
|
||||
try {
|
||||
await this.driver.waitUntil(async () => {
|
||||
const audioLevel = await this.getRemoteAudioLevel(testee);
|
||||
|
||||
if (muted) {
|
||||
if (audioLevel !== null && audioLevel > 0.1) {
|
||||
console.log(`muted exiting on: ${audioLevel}`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// When testing for unmuted we wait for first sound
|
||||
if (audioLevel !== null && audioLevel > 0.1) {
|
||||
console.log(`unmuted exiting on: ${audioLevel}`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
{ timeout });
|
||||
|
||||
// When testing for muted we don't want to have
|
||||
// the condition succeeded
|
||||
if (muted) {
|
||||
const name = await testee.displayName;
|
||||
|
||||
assert.fail(`There was some sound coming from muted: '${name}'`);
|
||||
} // else we're good for unmuted participant
|
||||
} catch (_timeoutE) {
|
||||
if (!muted) {
|
||||
const name = await testee.displayName;
|
||||
|
||||
assert.fail(`There was no sound from unmuted: '${name}'`);
|
||||
} // else we're good for muted participant
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for remote video state - receiving and displayed.
|
||||
* @param endpointId
|
||||
* @param reverse
|
||||
*/
|
||||
async waitForRemoteVideo(endpointId: string) {
|
||||
await this.driver.waitUntil(async () =>
|
||||
await this.driver.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
endpointId) && await this.driver.$(
|
||||
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected remote video for ${endpointId} to be received 15s by ${this.name}`
|
||||
});
|
||||
async waitForRemoteVideo(endpointId: string, reverse = false) {
|
||||
if (reverse) {
|
||||
await this.driver.waitUntil(async () =>
|
||||
!await this.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
endpointId) && !await this.driver.$(
|
||||
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected remote video for ${endpointId} to not be received 15s by ${this.displayName}`
|
||||
});
|
||||
} else {
|
||||
await this.driver.waitUntil(async () =>
|
||||
await this.execute(epId => JitsiMeetJS.app.testing.isRemoteVideoReceived(`${epId}`),
|
||||
endpointId) && await this.driver.$(
|
||||
`//span[@id="participant_${endpointId}" and contains(@class, "display-video")]`).isExisting(), {
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected remote video for ${endpointId} to be received 15s by ${this.displayName}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for ninja icon to be displayed.
|
||||
* @param endpointId
|
||||
* @param endpointId When no endpoint id is passed we check for any ninja icon.
|
||||
*/
|
||||
async waitForNinjaIcon(endpointId: string) {
|
||||
await this.driver.$(`//span[@id='participant_${endpointId}']//span[@class='connection_ninja']`)
|
||||
.waitForDisplayed({
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected ninja icon for ${endpointId} to be displayed in 15s by ${this.name}`
|
||||
});
|
||||
async waitForNinjaIcon(endpointId?: string) {
|
||||
if (endpointId) {
|
||||
await this.driver.$(`//span[@id='participant_${endpointId}']//span[@class='connection_ninja']`)
|
||||
.waitForDisplayed({
|
||||
timeout: 15_000,
|
||||
timeoutMsg: `expected ninja icon for ${endpointId} to be displayed in 15s by ${this.name}`
|
||||
});
|
||||
} else {
|
||||
await this.driver.$('//span[contains(@class,"videocontainer")]//span[contains(@class,"connection_ninja")]')
|
||||
.waitForDisplayed({
|
||||
timeout: 5_000,
|
||||
timeoutMsg: `expected ninja icon to be displayed in 5s by ${this.displayName}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for dominant speaker icon to appear in remote video of a participant.
|
||||
* @param endpointId the endpoint ID of the participant whose dominant speaker icon status will be checked.
|
||||
*/
|
||||
waitForDominantSpeaker(endpointId: string) {
|
||||
return this.driver.$(`//span[@id="participant_${endpointId}" and contains(@class, "dominant-speaker")]`)
|
||||
.waitForDisplayed({ timeout: 5_000 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,49 @@ export async function ensureThreeParticipants(ctx: IContext, options: IJoinOptio
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the first participant instance or prepares one for re-joining.
|
||||
*
|
||||
* @param {Object} ctx - The context.
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function joinFirstParticipant(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
|
||||
return joinTheModeratorAsP1(ctx, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the second participant instance or prepares one for re-joining.
|
||||
*
|
||||
* @param {Object} ctx - The context.
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function joinSecondParticipant(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
|
||||
return _joinParticipant('participant2', ctx.p2, p => {
|
||||
ctx.p2 = p;
|
||||
}, {
|
||||
displayName: P2_DISPLAY_NAME,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the third participant instance or prepares one for re-joining.
|
||||
*
|
||||
* @param {Object} ctx - The context.
|
||||
* @param {IJoinOptions} options - The options to use when joining the participant.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function joinThirdParticipant(ctx: IContext, options: IJoinOptions = {}): Promise<void> {
|
||||
return _joinParticipant('participant3', ctx.p3, p => {
|
||||
ctx.p3 = p;
|
||||
}, {
|
||||
displayName: P3_DISPLAY_NAME,
|
||||
...options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that there are four participants.
|
||||
*
|
||||
@@ -131,13 +174,14 @@ export async function ensureTwoParticipants(ctx: IContext, options: IJoinOptions
|
||||
|
||||
const { skipInMeetingChecks } = options;
|
||||
|
||||
await _joinParticipant('participant2', ctx.p2, p => {
|
||||
ctx.p2 = p;
|
||||
}, {
|
||||
displayName: P2_DISPLAY_NAME,
|
||||
...options
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
_joinParticipant('participant2', ctx.p2, p => {
|
||||
ctx.p2 = p;
|
||||
}, {
|
||||
displayName: P2_DISPLAY_NAME,
|
||||
...options
|
||||
}),
|
||||
skipInMeetingChecks ? Promise.resolve() : ctx.p1.waitForRemoteStreams(1),
|
||||
skipInMeetingChecks ? Promise.resolve() : ctx.p2.waitForRemoteStreams(1)
|
||||
]);
|
||||
@@ -215,7 +259,7 @@ export async function unmuteAudioAndCheck(testee: Participant, observer: Partici
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the video on testee and check on observer.
|
||||
* Stop the video on testee and check on observer.
|
||||
* @param testee
|
||||
* @param observer
|
||||
*/
|
||||
@@ -226,6 +270,18 @@ export async function unmuteVideoAndCheck(testee: Participant, observer: Partici
|
||||
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the video on testee and check on observer.
|
||||
* @param testee
|
||||
* @param observer
|
||||
*/
|
||||
export async function muteVideoAndCheck(testee: Participant, observer: Participant): Promise<void> {
|
||||
await testee.getToolbar().clickVideoMuteButton();
|
||||
|
||||
await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee);
|
||||
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JWT token for a moderator.
|
||||
*/
|
||||
@@ -302,3 +358,23 @@ export async function checkSubject(participant: Participant, subject: string) {
|
||||
|
||||
expect(txt.startsWith(subject)).toBe(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a screensharing tile is displayed on the observer.
|
||||
* Expects there was already a video by this participant and screen sharing will be the second video `-v1`.
|
||||
*/
|
||||
export async function checkForScreensharingTile(sharer: Participant, observer: Participant, reverse = false) {
|
||||
await observer.driver.$(`//span[@id='participant_${await sharer.getEndpointId()}-v1']`).waitForDisplayed({
|
||||
timeout: 3_000,
|
||||
reverse
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Hangs up all participants (p1, p2, p3 and p4)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export function hangupAllParticipants() {
|
||||
return Promise.all([ ctx.p1?.hangup(), ctx.p2?.hangup(), ctx.p3?.hangup(), ctx.p4?.hangup() ]
|
||||
.map(p => p ?? Promise.resolve()));
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export type IContext = {
|
||||
p2: Participant;
|
||||
p3: Participant;
|
||||
p4: Participant;
|
||||
roomKey: string;
|
||||
roomName: string;
|
||||
skipSuiteTests: boolean;
|
||||
times: any;
|
||||
|
||||
@@ -36,7 +36,7 @@ export default class Filmstrip extends BasePageObject {
|
||||
|
||||
await this.participant.driver.$(mutedIconXPath).waitForDisplayed({
|
||||
reverse,
|
||||
timeout: 2000,
|
||||
timeout: 5_000,
|
||||
timeoutMsg: `Audio mute icon is${reverse ? '' : ' not'} displayed for ${testee.name}`
|
||||
});
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export default class Filmstrip extends BasePageObject {
|
||||
|
||||
await remoteDisplayName.moveTo();
|
||||
|
||||
return await this.participant.driver.execute(eId =>
|
||||
return await this.participant.execute(eId =>
|
||||
document.evaluate(`//span[@id="participant_${eId}"]//video`,
|
||||
document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue?.srcObject?.id, endpointId);
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export default class Filmstrip extends BasePageObject {
|
||||
* Returns the local video id.
|
||||
*/
|
||||
getLocalVideoId() {
|
||||
return this.participant.driver.execute(
|
||||
return this.participant.execute(
|
||||
'return document.getElementById("localVideo_container").srcObject.id');
|
||||
}
|
||||
|
||||
@@ -80,10 +80,49 @@ export default class Filmstrip extends BasePageObject {
|
||||
* @param participant The participant.
|
||||
*/
|
||||
async pinParticipant(participant: Participant) {
|
||||
const id = participant === this.participant
|
||||
? 'localVideoContainer' : `participant_${await participant.getEndpointId()}`;
|
||||
let videoIdToSwitchTo;
|
||||
|
||||
await this.participant.driver.$(`//span[@id="${id}"]`).click();
|
||||
if (participant === this.participant) {
|
||||
videoIdToSwitchTo = await this.getLocalVideoId();
|
||||
|
||||
// when looking up the element and clicking it, it doesn't work if we do it twice in a row (oneOnOne.spec)
|
||||
await this.participant.execute(() => document?.getElementById('localVideoContainer')?.click());
|
||||
} else {
|
||||
const epId = await participant.getEndpointId();
|
||||
|
||||
videoIdToSwitchTo = await this.getRemoteVideoId(epId);
|
||||
|
||||
await this.participant.driver.$(`//span[@id="participant_${epId}"]`).click();
|
||||
}
|
||||
|
||||
await this.participant.driver.waitUntil(
|
||||
async () => await this.participant.getLargeVideo().getId() === videoIdToSwitchTo,
|
||||
{
|
||||
timeout: 3_000,
|
||||
timeoutMsg: `${this.participant.displayName} did not switch the large video to ${
|
||||
participant.displayName}`
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpins a participant by clicking on their thumbnail.
|
||||
* @param participant
|
||||
*/
|
||||
async unpinParticipant(participant: Participant) {
|
||||
const epId = await participant.getEndpointId();
|
||||
|
||||
if (participant === this.participant) {
|
||||
await this.participant.execute(() => document?.getElementById('localVideoContainer')?.click());
|
||||
} else {
|
||||
await this.participant.driver.$(`//span[@id="participant_${epId}"]`).click();
|
||||
}
|
||||
|
||||
await this.participant.driver.$(`//div[ @id="pin-indicator-${epId}" ]`).waitForDisplayed({
|
||||
timeout: 2_000,
|
||||
timeoutMsg: `${this.participant.displayName} did not unpin ${participant.displayName}`,
|
||||
reverse: true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,11 +175,7 @@ export default class Filmstrip extends BasePageObject {
|
||||
* @param participant
|
||||
*/
|
||||
async muteAudio(participant: Participant) {
|
||||
const participantId = await participant.getEndpointId();
|
||||
|
||||
await this.participant.driver.$(`#participant-item-${participantId}`).moveTo();
|
||||
|
||||
await this.participant.driver.$(`button[data-testid="mute-audio-${participantId}"]`).click();
|
||||
await this.clickOnRemoteMenuLink(await participant.getEndpointId(), 'mutelink', false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,12 +194,19 @@ export default class Filmstrip extends BasePageObject {
|
||||
return this.clickOnRemoteMenuLink(participantId, 'kicklink', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hover over local video.
|
||||
*/
|
||||
hoverOverLocalVideo() {
|
||||
return this.participant.driver.$(LOCAL_VIDEO_MENU_TRIGGER).moveTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks on the hide self view button from local video.
|
||||
*/
|
||||
async hideSelfView() {
|
||||
// open local video menu
|
||||
await this.participant.driver.$(LOCAL_VIDEO_MENU_TRIGGER).moveTo();
|
||||
await this.hoverOverLocalVideo();
|
||||
await this.participant.driver.$(LOCAL_USER_CONTROLS).moveTo();
|
||||
|
||||
// click Hide self view button
|
||||
@@ -219,4 +261,16 @@ export default class Filmstrip extends BasePageObject {
|
||||
return (await this.participant.driver.$$('//div[@id="remoteVideos"]//span[contains(@class,"videocontainer")]')
|
||||
.filter(thumbnail => thumbnail.isDisplayed())).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if remote videos in filmstrip are visible.
|
||||
*
|
||||
* @param isDisplayed whether or not filmstrip remote videos should be visible
|
||||
*/
|
||||
verifyRemoteVideosDisplay(isDisplayed: boolean) {
|
||||
return this.participant.driver.$('//div[contains(@class, "remote-videos")]/div').waitForDisplayed({
|
||||
timeout: 5_000,
|
||||
reverse: !isDisplayed,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default class IframeAPI extends BasePageObject {
|
||||
* @param event
|
||||
*/
|
||||
getEventResult(event: string): Promise<any> {
|
||||
return this.participant.driver.execute(
|
||||
return this.participant.execute(
|
||||
eventName => {
|
||||
const result = window.jitsiAPI.test[eventName];
|
||||
|
||||
@@ -28,7 +28,7 @@ export default class IframeAPI extends BasePageObject {
|
||||
* @param eventName The event name.
|
||||
*/
|
||||
addEventListener(eventName: string) {
|
||||
return this.participant.driver.execute(
|
||||
return this.participant.execute(
|
||||
(event, prefix) => {
|
||||
console.log(`${new Date().toISOString()} ${prefix} Adding listener for event: ${event}`);
|
||||
window.jitsiAPI.addListener(event, evt => {
|
||||
@@ -43,14 +43,14 @@ export default class IframeAPI extends BasePageObject {
|
||||
* Returns an array of available rooms and details of it.
|
||||
*/
|
||||
getRoomsInfo() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.getRoomsInfo());
|
||||
return this.participant.execute(() => window.jitsiAPI.getRoomsInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of participants in the conference.
|
||||
*/
|
||||
getNumberOfParticipants() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.getNumberOfParticipants());
|
||||
return this.participant.execute(() => window.jitsiAPI.getNumberOfParticipants());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export default class IframeAPI extends BasePageObject {
|
||||
* @param args The arguments.
|
||||
*/
|
||||
executeCommand(command: string, ...args: any[]) {
|
||||
return this.participant.driver.execute(
|
||||
return this.participant.execute(
|
||||
(commandName, commandArgs) =>
|
||||
window.jitsiAPI.executeCommand(commandName, ...commandArgs)
|
||||
, command, args);
|
||||
@@ -69,13 +69,13 @@ export default class IframeAPI extends BasePageObject {
|
||||
* Returns the current state of the participant's pane.
|
||||
*/
|
||||
isParticipantsPaneOpen() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.isParticipantsPaneOpen());
|
||||
return this.participant.execute(() => window.jitsiAPI.isParticipantsPaneOpen());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the embedded Jitsi Meet conference.
|
||||
*/
|
||||
dispose() {
|
||||
return this.participant.driver.execute(() => window.jitsiAPI.dispose());
|
||||
return this.participant.execute(() => window.jitsiAPI.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
81
tests/pageobjects/LargeVideo.ts
Normal file
81
tests/pageobjects/LargeVideo.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import BasePageObject from './BasePageObject';
|
||||
|
||||
/**
|
||||
* The large video.
|
||||
*/
|
||||
export default class LargeVideo extends BasePageObject {
|
||||
/**
|
||||
* Returns the elapsed time at which video has been playing.
|
||||
*
|
||||
* @return {number} - The current play time of the video element.
|
||||
*/
|
||||
async getPlaytime() {
|
||||
return this.participant.driver.$('#largeVideo').getProperty('currentTime');
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits 5s for the large video to switch to passed endpoint id.
|
||||
*
|
||||
* @param {string} endpointId - The endpoint.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
waitForSwitchTo(endpointId: string): Promise<void> {
|
||||
return this.participant.driver.waitUntil(async () => endpointId === await this.getResource(), {
|
||||
timeout: 5_000,
|
||||
timeoutMsg: `expected large video to switch to ${endpointId} for 5s`
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets avatar SRC attribute for the one displayed on large video.
|
||||
*/
|
||||
async getAvatar() {
|
||||
const avatar = this.participant.driver.$('//img[@id="dominantSpeakerAvatar"]');
|
||||
|
||||
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns resource part of the JID of the user who is currently displayed in the large video area.
|
||||
*/
|
||||
getResource() {
|
||||
return this.participant.execute(() => APP?.UI?.getLargeVideoID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source of the large video currently shown.
|
||||
*/
|
||||
getId() {
|
||||
return this.participant.execute('return document.getElementById("largeVideo").srcObject.id');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the large video is playing or not.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
assertPlaying() {
|
||||
let lastTime: number;
|
||||
|
||||
return this.participant.driver.waitUntil(async () => {
|
||||
const currentTime = parseFloat(await this.getPlaytime());
|
||||
|
||||
if (typeof lastTime === 'undefined') {
|
||||
lastTime = currentTime;
|
||||
}
|
||||
if (currentTime > lastTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lastTime = currentTime;
|
||||
|
||||
return false;
|
||||
}, {
|
||||
timeout: 5_500,
|
||||
interval: 500,
|
||||
timeoutMsg:
|
||||
`Expected large video for participant ${this.participant.name} to play but it didn't for more than 5s`
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export default class Notifications extends BasePageObject {
|
||||
private async getNotificationText(testId: string) {
|
||||
const notificationElement = this.participant.driver.$(`[data-testid="${testId}"]`);
|
||||
|
||||
await notificationElement.waitForExist();
|
||||
await notificationElement.waitForExist({ timeout: 2_000 });
|
||||
|
||||
return await notificationElement.getText();
|
||||
}
|
||||
|
||||
@@ -239,4 +239,16 @@ export default class ParticipantsPane extends BasePageObject {
|
||||
await rejectButton.waitForExist();
|
||||
await rejectButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutes the audio of a participant.
|
||||
* @param participant
|
||||
*/
|
||||
async muteAudio(participant: Participant) {
|
||||
const participantId = await participant.getEndpointId();
|
||||
|
||||
await this.participant.driver.$(`#participant-item-${participantId}`).moveTo();
|
||||
|
||||
await this.participant.driver.$(`button[data-testid="mute-audio-${participantId}"]`).click();
|
||||
}
|
||||
}
|
||||
|
||||
37
tests/pageobjects/PasswordDialog.ts
Normal file
37
tests/pageobjects/PasswordDialog.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import BaseDialog from './BaseDialog';
|
||||
|
||||
const INPUT_KEY_XPATH = '//input[@name="lockKey"]';
|
||||
|
||||
/**
|
||||
* Represents the password dialog in a particular participant.
|
||||
*/
|
||||
export default class PasswordDialog extends BaseDialog {
|
||||
/**
|
||||
* Waiting for the dialog to appear.
|
||||
*/
|
||||
async waitForDialog() {
|
||||
const input = this.participant.driver.$(INPUT_KEY_XPATH);
|
||||
|
||||
await input.waitForExist({
|
||||
timeout: 5000,
|
||||
timeoutMsg: 'Password dialog not found'
|
||||
});
|
||||
await input.waitForDisplayed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a password and submits the dialog.
|
||||
* @param password
|
||||
*/
|
||||
async submitPassword(password: string) {
|
||||
const passwordInput = this.participant.driver.$(INPUT_KEY_XPATH);
|
||||
|
||||
await passwordInput.waitForExist();
|
||||
await passwordInput.click();
|
||||
await passwordInput.clearValue();
|
||||
|
||||
await this.participant.driver.keys(password);
|
||||
|
||||
await this.clickOkButton();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import PreMeetingScreen from './PreMeetingScreen';
|
||||
|
||||
const DISPLAY_NAME_ID = 'premeeting-name-input';
|
||||
const ERROR_ON_JOIN = 'prejoin.errorMessage';
|
||||
const JOIN_BUTTON_TEST_ID = 'prejoin.joinMeeting';
|
||||
const JOIN_WITHOUT_AUDIO = 'prejoin.joinWithoutAudio';
|
||||
const OPTIONS_BUTTON = 'prejoin.joinOptions';
|
||||
|
||||
/**
|
||||
* Page object for the PreJoin screen.
|
||||
@@ -28,4 +31,25 @@ export default class PreJoinScreen extends PreMeetingScreen {
|
||||
return this.participant.driver.$('[data-testid="prejoin.screen"]')
|
||||
.waitForDisplayed({ timeout: 3000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the error message displayed on the prejoin screen.
|
||||
*/
|
||||
getErrorOnJoin() {
|
||||
return this.participant.driver.$(`[data-testid="${ERROR_ON_JOIN}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the join without audio button element.
|
||||
*/
|
||||
getJoinWithoutAudioButton() {
|
||||
return this.participant.driver.$(`[data-testid="${JOIN_WITHOUT_AUDIO}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the join options button element.
|
||||
*/
|
||||
getJoinOptions() {
|
||||
return this.participant.driver.$(`[data-testid="${OPTIONS_BUTTON}"]`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ export default abstract class PreMeetingScreen extends BasePageObject {
|
||||
* Checks internally whether lobby room is joined.
|
||||
*/
|
||||
isLobbyRoomJoined() {
|
||||
return this.participant.driver.execute(
|
||||
() => APP.conference._room?.room?.getLobby()?.lobbyRoom?.joined === true);
|
||||
return this.participant.execute(
|
||||
() => APP?.conference?._room?.room?.getLobby()?.lobbyRoom?.joined === true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ const ADD_PASSWORD_FIELD = 'info-password-input';
|
||||
const DIALOG_CONTAINER = 'security-dialog';
|
||||
const LOCAL_LOCK = 'info-password-local';
|
||||
const REMOTE_LOCK = 'info-password-remote';
|
||||
const REMOVE_PASSWORD = 'remove-password';
|
||||
|
||||
/**
|
||||
* Page object for the security dialog.
|
||||
@@ -133,4 +134,18 @@ export default class SecurityDialog extends BaseDialog {
|
||||
expect(validationMessage).toBe('');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the password from the current conference through the security dialog, if a password is set.
|
||||
*/
|
||||
async removePassword() {
|
||||
if (!await this.isLocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removePassword = this.participant.driver.$(`.${REMOVE_PASSWORD}`);
|
||||
|
||||
await removePassword.waitForClickable();
|
||||
await removePassword.click();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user