Compare commits
68 Commits
7467
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f84a561d9e | ||
|
|
295878ffff | ||
|
|
609942654a | ||
|
|
60ad0196c3 | ||
|
|
caea6966ef | ||
|
|
d4c269f7cb | ||
|
|
54a1ee53b4 | ||
|
|
2c51e8ac06 | ||
|
|
3cbd69eef2 | ||
|
|
ee539644d8 | ||
|
|
465263bc97 | ||
|
|
1def65eb90 | ||
|
|
746be98bfc | ||
|
|
99b58dd318 | ||
|
|
df3ef0d895 | ||
|
|
83e4042668 | ||
|
|
c6e87568b6 | ||
|
|
0170c65c7b | ||
|
|
a7c1ccec71 | ||
|
|
1adbebf9dc | ||
|
|
9d68cb52b3 | ||
|
|
44272b650c | ||
|
|
5ce96d379a | ||
|
|
173c5fe430 | ||
|
|
e10595c3ed | ||
|
|
9138f56701 | ||
|
|
974e2a5106 | ||
|
|
509cf661f5 | ||
|
|
25fdea9984 | ||
|
|
9979e470fc | ||
|
|
2a492f5036 | ||
|
|
baf1f01e44 | ||
|
|
1f8dc944e3 | ||
|
|
dc07c6fede | ||
|
|
94a63f8aea | ||
|
|
a47cb595db | ||
|
|
86ccc176e8 | ||
|
|
b31041f0ce | ||
|
|
e434a78de9 | ||
|
|
6ddb77e03c | ||
|
|
f6665d79c0 | ||
|
|
75a7b99a42 | ||
|
|
3858a40c1c | ||
|
|
3d6aa8f2b5 | ||
|
|
54436f97c1 | ||
|
|
dca40dc6cb | ||
|
|
c19d91a373 | ||
|
|
840cfd8ab0 | ||
|
|
8cf6ba88e1 | ||
|
|
4ac81b030e | ||
|
|
e5cd1b29fe | ||
|
|
ebc932572f | ||
|
|
f0a6c6e67e | ||
|
|
e1454b5c4a | ||
|
|
f6e2abdf01 | ||
|
|
e2a02f4b21 | ||
|
|
934d7db24e | ||
|
|
2b520cbc4c | ||
|
|
e37dd73b9e | ||
|
|
c646319657 | ||
|
|
7a3a5a3f43 | ||
|
|
5345a77092 | ||
|
|
91de33550d | ||
|
|
85fb7513db | ||
|
|
c9d907e3fe | ||
|
|
5dfd02151e | ||
|
|
e446802ac9 | ||
|
|
bb71a4bb7d |
@@ -10,7 +10,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.1'
|
||||
classpath 'com.android.tools.build:gradle:7.3.1'
|
||||
classpath 'com.google.gms:google-services:4.3.14'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2'
|
||||
}
|
||||
@@ -18,21 +18,16 @@ buildscript {
|
||||
|
||||
ext {
|
||||
kotlinVersion = "1.7.0"
|
||||
buildToolsVersion = "31.0.0"
|
||||
compileSdkVersion = 32
|
||||
buildToolsVersion = "33.0.2"
|
||||
compileSdkVersion = 33
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 32
|
||||
targetSdkVersion = 33
|
||||
supportLibVersion = "28.0.0"
|
||||
|
||||
if (System.properties['os.arch'] == "aarch64") {
|
||||
// For M1 Users we need to use the NDK 24 which added support for aarch64
|
||||
ndkVersion = "24.0.8215888"
|
||||
} else {
|
||||
// Otherwise we default to the side-by-side NDK version from AGP.
|
||||
ndkVersion = "21.4.7075529"
|
||||
}
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
// The Maven artifact groupdId of the third-party react-native modules which
|
||||
// The Maven artifact groupId of the third-party react-native modules which
|
||||
// Jitsi Meet SDK for Android depends on and which are not available in
|
||||
// third-party Maven repositories so we have to deploy to a Maven repository
|
||||
// of ours.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -130,6 +130,7 @@ import {
|
||||
isUserInteractionRequiredForUnmute
|
||||
} from './react/features/base/tracks/functions';
|
||||
import { downloadJSON } from './react/features/base/util/downloadJSON';
|
||||
import { openLeaveReasonDialog } from './react/features/conference/actions.web';
|
||||
import { showDesktopPicker } from './react/features/desktop-picker/actions';
|
||||
import { appendSuffix } from './react/features/display-name/functions';
|
||||
import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedback/actions';
|
||||
@@ -441,7 +442,7 @@ export default {
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics (such as "No audio input" or
|
||||
// "Are you trying to speak?" ) even if the user joins the conference muted.
|
||||
const initialDevices = config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
|
||||
const initialDevices = config.startSilent || config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
|
||||
const requestedAudio = !config.disableInitialGUM;
|
||||
let requestedVideo = false;
|
||||
|
||||
@@ -2428,9 +2429,10 @@ export default {
|
||||
/**
|
||||
* Disconnect from the conference and optionally request user feedback.
|
||||
* @param {boolean} [requestFeedback=false] if user feedback should be
|
||||
* @param {string} [hangupReason] the reason for leaving the meeting
|
||||
* requested
|
||||
*/
|
||||
hangup(requestFeedback = false) {
|
||||
async hangup(requestFeedback = false, hangupReason) {
|
||||
APP.store.dispatch(disableReceiver());
|
||||
|
||||
this._stopProxyConnection();
|
||||
@@ -2447,36 +2449,33 @@ export default {
|
||||
|
||||
APP.UI.removeAllListeners();
|
||||
|
||||
let requestFeedbackPromise;
|
||||
let feedbackResult = {};
|
||||
|
||||
if (requestFeedback) {
|
||||
requestFeedbackPromise
|
||||
= APP.store.dispatch(maybeOpenFeedbackDialog(room))
|
||||
|
||||
// false because the thank you dialog shouldn't be displayed
|
||||
.catch(() => Promise.resolve(false));
|
||||
} else {
|
||||
requestFeedbackPromise = Promise.resolve(true);
|
||||
try {
|
||||
feedbackResult = await APP.store.dispatch(maybeOpenFeedbackDialog(room, hangupReason));
|
||||
} catch (err) { // eslint-disable-line no-empty
|
||||
}
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
requestFeedbackPromise,
|
||||
this.leaveRoom()
|
||||
])
|
||||
.then(values => {
|
||||
this._room = undefined;
|
||||
room = undefined;
|
||||
if (!feedbackResult.wasDialogShown && hangupReason) {
|
||||
await APP.store.dispatch(openLeaveReasonDialog(hangupReason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
|
||||
* and let the page take care of sending the message, since there will be
|
||||
* a redirect to the page regardlessly.
|
||||
*/
|
||||
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
APP.store.dispatch(maybeRedirectToWelcomePage(values[0]));
|
||||
});
|
||||
await this.leaveRoom();
|
||||
|
||||
this._room = undefined;
|
||||
room = undefined;
|
||||
|
||||
/**
|
||||
* Don't call {@code notifyReadyToClose} if the promotional page flag is set
|
||||
* and let the page take care of sending the message, since there will be
|
||||
* a redirect to the page anyway.
|
||||
*/
|
||||
if (!interfaceConfig.SHOW_PROMOTIONAL_CLOSE_PAGE) {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
APP.store.dispatch(maybeRedirectToWelcomePage(feedbackResult));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -871,7 +871,7 @@ var config = {
|
||||
// customParticipantMenuButtons: [],
|
||||
|
||||
// An array with custom option buttons for the toolbar
|
||||
// type: Array<{ icon: string; id: string; text: string; }>
|
||||
// type: Array<{ icon: string; id: string; text: string; backgroundColor?: string; }>
|
||||
// customToolbarButtons: [],
|
||||
|
||||
// Stats
|
||||
@@ -1572,6 +1572,8 @@ var config = {
|
||||
// disableFilmstripAutohiding: false,
|
||||
|
||||
// filmstrip: {
|
||||
// // Disable the vertical/horizonal filmstrip.
|
||||
// disabled: false,
|
||||
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
|
||||
// // (width, tiles aspect ratios) through the interfaceConfig options.
|
||||
// disableResizable: false,
|
||||
@@ -1594,6 +1596,8 @@ var config = {
|
||||
|
||||
// Tile view related config options.
|
||||
// tileView: {
|
||||
// // Whether tileview should be disabled.
|
||||
// disabled: false,
|
||||
// // The optimal number of tiles that are going to be shown in tile view. Depending on the screen size it may
|
||||
// // not be possible to show the exact number of participants specified here.
|
||||
// numberOfVisibleTiles: 25,
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
.drawer-portal {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 351;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
||||
&.notification-portal {
|
||||
z-index: 901;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-portal::after {
|
||||
content: '';
|
||||
background-color: #141414;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.drawer-menu-container {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.drawer-menu {
|
||||
overflow-y: auto;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
&#{&} .overflow-menu {
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
height: calc(80vh - 144px - 64px);
|
||||
overflow-y: auto;
|
||||
|
||||
.overflow-menu-item {
|
||||
box-sizing: border-box;
|
||||
height: 48px;
|
||||
padding: 12px 16px;
|
||||
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 16px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,3 @@
|
||||
.participants_pane {
|
||||
background-color: #141414;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
width: 315px;
|
||||
z-index: $zindex0;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 600;
|
||||
height: 100%;
|
||||
width: 315px;
|
||||
|
||||
& > *:first-child,
|
||||
& > *:last-child {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 580px) {
|
||||
.participants_pane {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.participants_pane-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.jitsi-icon {
|
||||
&-dominant-speaker {
|
||||
background-color: #1EC26A;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
.hint-msg{
|
||||
.hint-msg {
|
||||
p {
|
||||
margin: 26px auto;
|
||||
font-weight: 600;
|
||||
@@ -31,4 +31,10 @@
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.forbidden-msg {
|
||||
p {
|
||||
font-size: 16px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'modals/invite/invite_more';
|
||||
@import 'modals/security/security';
|
||||
@import 'responsive';
|
||||
@import 'drawer';
|
||||
@import 'participants-pane';
|
||||
@import 'reactions-menu';
|
||||
@import 'plan-limit';
|
||||
|
||||
5
debian/jitsi-meet-tokens.postinst
vendored
5
debian/jitsi-meet-tokens.postinst
vendored
@@ -38,6 +38,11 @@ case "$1" in
|
||||
if [ "$RET" = "false" ] ; then
|
||||
echo "Application secret is mandatory"
|
||||
fi
|
||||
# Not allowed unix special characters in secret: /, \, ", ', `
|
||||
if echo "$RET" | grep -q '[/\\\"\`]' ; then
|
||||
echo "Application secret contains invalid characters: /, \\, \", ', \`"
|
||||
exit 1
|
||||
fi
|
||||
APP_SECRET=$RET
|
||||
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
|
||||
|
||||
157
ios/Podfile.lock
157
ios/Podfile.lock
@@ -3,11 +3,11 @@ PODS:
|
||||
- amplitude-react-native (2.7.0):
|
||||
- Amplitude (= 8.7.1)
|
||||
- React-Core
|
||||
- AppAuth (1.6.1):
|
||||
- AppAuth/Core (= 1.6.1)
|
||||
- AppAuth/ExternalUserAgent (= 1.6.1)
|
||||
- AppAuth/Core (1.6.1)
|
||||
- AppAuth/ExternalUserAgent (1.6.1):
|
||||
- AppAuth (1.6.2):
|
||||
- AppAuth/Core (= 1.6.2)
|
||||
- AppAuth/ExternalUserAgent (= 1.6.2)
|
||||
- AppAuth/Core (1.6.2)
|
||||
- AppAuth/ExternalUserAgent (1.6.2):
|
||||
- AppAuth/Core
|
||||
- boost (1.76.0)
|
||||
- CocoaLumberjack (3.7.2):
|
||||
@@ -103,56 +103,59 @@ PODS:
|
||||
- GoogleUtilities/Network (~> 7.7)
|
||||
- "GoogleUtilities/NSData+zlib (~> 7.7)"
|
||||
- nanopb (~> 2.30908.0)
|
||||
- GoogleDataTransport (9.2.2):
|
||||
- GoogleDataTransport (9.2.5):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleSignIn (6.2.4):
|
||||
- GoogleSignIn (7.0.0):
|
||||
- AppAuth (~> 1.5)
|
||||
- GTMAppAuth (~> 1.3)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.1):
|
||||
- GTMAppAuth (< 3.0, >= 1.3)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.1)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Environment (7.11.1):
|
||||
- GoogleUtilities/Environment (7.11.5):
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.11.1):
|
||||
- GoogleUtilities/Logger (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/MethodSwizzler (7.11.1):
|
||||
- GoogleUtilities/MethodSwizzler (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network (7.11.1):
|
||||
- GoogleUtilities/Network (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (7.11.1)"
|
||||
- GoogleUtilities/Reachability (7.11.1):
|
||||
- "GoogleUtilities/NSData+zlib (7.11.5)"
|
||||
- GoogleUtilities/Reachability (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/UserDefaults (7.11.1):
|
||||
- GoogleUtilities/UserDefaults (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMAppAuth (1.3.1):
|
||||
- GTMAppAuth (2.0.0):
|
||||
- AppAuth/Core (~> 1.6)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (2.3.0)
|
||||
- GTMSessionFetcher/Core (< 4.0, >= 1.5)
|
||||
- GTMSessionFetcher/Core (3.1.1)
|
||||
- JitsiWebRTC (111.0.2)
|
||||
- libwebp (1.2.4):
|
||||
- libwebp/demux (= 1.2.4)
|
||||
- libwebp/mux (= 1.2.4)
|
||||
- libwebp/webp (= 1.2.4)
|
||||
- libwebp/demux (1.2.4):
|
||||
- libwebp (1.3.1):
|
||||
- libwebp/demux (= 1.3.1)
|
||||
- libwebp/mux (= 1.3.1)
|
||||
- libwebp/sharpyuv (= 1.3.1)
|
||||
- libwebp/webp (= 1.3.1)
|
||||
- libwebp/demux (1.3.1):
|
||||
- libwebp/webp
|
||||
- libwebp/mux (1.2.4):
|
||||
- libwebp/mux (1.3.1):
|
||||
- libwebp/demux
|
||||
- libwebp/webp (1.2.4)
|
||||
- libwebp/sharpyuv (1.3.1)
|
||||
- libwebp/webp (1.3.1):
|
||||
- libwebp/sharpyuv
|
||||
- nanopb (2.30908.0):
|
||||
- nanopb/decode (= 2.30908.0)
|
||||
- nanopb/encode (= 2.30908.0)
|
||||
- nanopb/decode (2.30908.0)
|
||||
- nanopb/encode (2.30908.0)
|
||||
- ObjectiveDropboxOfficial (6.2.3)
|
||||
- PromisesObjC (2.2.0)
|
||||
- PromisesSwift (2.2.0):
|
||||
- PromisesObjC (= 2.2.0)
|
||||
- PromisesObjC (2.3.1)
|
||||
- PromisesSwift (2.3.1):
|
||||
- PromisesObjC (= 2.3.1)
|
||||
- RCT-Folly (2021.06.28.00-v2):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -364,36 +367,34 @@ PODS:
|
||||
- glog
|
||||
- react-native-background-timer (2.4.1):
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.7.2):
|
||||
- react-native-get-random-values (1.9.0):
|
||||
- React-Core
|
||||
- react-native-keep-awake (4.0.0):
|
||||
- React
|
||||
- react-native-netinfo (7.1.7):
|
||||
- react-native-netinfo (9.4.1):
|
||||
- React-Core
|
||||
- react-native-orientation-locker (1.5.0):
|
||||
- React-Core
|
||||
- react-native-pager-view (5.4.9):
|
||||
- react-native-pager-view (6.2.0):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.6.4):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- react-native-performance (5.0.0):
|
||||
- React-Core
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-slider (4.1.12):
|
||||
- react-native-safe-area-context (4.7.1):
|
||||
- React-Core
|
||||
- react-native-slider (4.4.3):
|
||||
- React-Core
|
||||
- react-native-splash-screen (3.3.0):
|
||||
- React-Core
|
||||
- react-native-video (6.0.0-alpha.1):
|
||||
- react-native-video (6.0.0-alpha.7):
|
||||
- React-Core
|
||||
- react-native-video/Video (= 6.0.0-alpha.1)
|
||||
- react-native-video/Video (6.0.0-alpha.1):
|
||||
- react-native-video/Video (= 6.0.0-alpha.7)
|
||||
- react-native-video/Video (6.0.0-alpha.7):
|
||||
- PromisesSwift
|
||||
- React-Core
|
||||
- react-native-webrtc (111.0.3):
|
||||
- JitsiWebRTC (~> 111.0.0)
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
- react-native-webview (13.5.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.69.11)
|
||||
- React-RCTActionSheet (0.69.11):
|
||||
@@ -463,30 +464,30 @@ PODS:
|
||||
- React-perflogger (= 0.69.11)
|
||||
- RNCalendarEvents (2.2.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.17.3):
|
||||
- RNCAsyncStorage (1.19.3):
|
||||
- React-Core
|
||||
- RNCClipboard (1.5.1):
|
||||
- React-Core
|
||||
- RNDefaultPreference (1.4.4):
|
||||
- React-Core
|
||||
- RNDeviceInfo (8.4.8):
|
||||
- RNDeviceInfo (10.9.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.9.0):
|
||||
- React-Core
|
||||
- RNGoogleSignin (9.0.2):
|
||||
- GoogleSignIn (~> 6.2)
|
||||
- RNGoogleSignin (10.0.1):
|
||||
- GoogleSignIn (~> 7.0)
|
||||
- React-Core
|
||||
- RNScreens (3.22.0):
|
||||
- RNScreens (3.24.0):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNSound (0.11.1):
|
||||
- RNSound (0.11.2):
|
||||
- React-Core
|
||||
- RNSound/Core (= 0.11.1)
|
||||
- RNSound/Core (0.11.1):
|
||||
- RNSound/Core (= 0.11.2)
|
||||
- RNSound/Core (0.11.2):
|
||||
- React-Core
|
||||
- RNSVG (12.4.3):
|
||||
- RNSVG (13.13.0):
|
||||
- React-Core
|
||||
- RNWatch (1.0.11):
|
||||
- RNWatch (1.1.0):
|
||||
- React
|
||||
- Yoga (1.14.0)
|
||||
|
||||
@@ -525,6 +526,7 @@ DEPENDENCIES:
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- react-native-performance (from `../node_modules/react-native-performance`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||
@@ -638,6 +640,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-orientation-locker"
|
||||
react-native-pager-view:
|
||||
:path: "../node_modules/react-native-pager-view"
|
||||
react-native-performance:
|
||||
:path: "../node_modules/react-native-performance"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-slider:
|
||||
@@ -702,7 +706,7 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
Amplitude: 834c7332dfb9640a751e21c13efb22a07c0c12d4
|
||||
amplitude-react-native: 0ed8cab759aafaa94961b82122bf56297da607ad
|
||||
AppAuth: e48b432bb4ba88b10cb2bcc50d7f3af21e78b9c2
|
||||
AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
@@ -720,17 +724,17 @@ SPEC CHECKSUMS:
|
||||
giphy-react-native-sdk: fcda9639f8ca2cc47e0517b6ef11c19359db5f5a
|
||||
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
|
||||
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
|
||||
GoogleDataTransport: 8378d1fa8ac49753ea6ce70d65a7cb70ce5f66e6
|
||||
GoogleSignIn: 5651ce3a61e56ca864160e79b484cd9ed3f49b7a
|
||||
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
||||
GTMAppAuth: 0ff230db599948a9ad7470ca667337803b3fc4dd
|
||||
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
|
||||
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
|
||||
GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842
|
||||
GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
|
||||
GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae
|
||||
GTMSessionFetcher: e8647203b65cee28c5f73d0f473d096653945e72
|
||||
JitsiWebRTC: 80f62908fcf2a1160e0d14b584323fb6e6be630b
|
||||
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef
|
||||
libwebp: 33dc822fbbf4503668d09f7885bbfedc76c45e96
|
||||
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
|
||||
ObjectiveDropboxOfficial: fe206ce8c0bc49976c249d472db7fdbc53ebbd53
|
||||
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
||||
PromisesSwift: cf9eb58666a43bbe007302226e510b16c1e10959
|
||||
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
|
||||
PromisesSwift: 28dca69a9c40779916ac2d6985a0192a5cb4a265
|
||||
RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a
|
||||
RCTRequired: 8e9a57dddc8f8e9e816c67c2d2537271a997137a
|
||||
RCTTypeSafety: 2b19e268e2036a2c2f6db6deb1ac03e28b1d607a
|
||||
@@ -746,17 +750,18 @@ SPEC CHECKSUMS:
|
||||
React-jsinspector: 9ca5bf73ed0a195397e45fdbcd507cf7d503c428
|
||||
React-logger: 700340e325f21ba2a2d6413a61ef14268c7360aa
|
||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
||||
react-native-netinfo: 27f287f2d191693f3b9d01a4273137fcf91c3b5d
|
||||
react-native-netinfo: fefd4e98d75cbdd6e85fc530f7111a8afdf2b0c5
|
||||
react-native-orientation-locker: 851f6510d8046ea2f14aa169b1e01fcd309a94ba
|
||||
react-native-pager-view: 3ee7d4c7697fb3ef788346e834a60cca97ed8540
|
||||
react-native-safe-area-context: 68b07eabfb0d14547d36f6929c0e98d818064f02
|
||||
react-native-slider: 6e9b86e76cce4b9e35b3403193a6432ed07e0c81
|
||||
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
|
||||
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
|
||||
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-video: 967eead48aaa42c25a9e1d65c3b1ab30762a88df
|
||||
react-native-webrtc: 4d1669c2ed29767fe70b0169428b4466589ecf8b
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
react-native-webview: 8baa0f5c6d336d6ba488e942bcadea5bf51f050a
|
||||
React-perflogger: fdee2a0c512167ae4c19c4e230ccf6aa66a6aff0
|
||||
React-RCTActionSheet: 1cf5fef4e372f1c877969710a51bea4bb25e78fe
|
||||
React-RCTAnimation: 73816e3acd1f5e3f00166fc7eedb34f6b112f734
|
||||
@@ -770,16 +775,16 @@ SPEC CHECKSUMS:
|
||||
React-runtimeexecutor: 59407514818b2afbb1d7507e4e1ac834d24b0fbd
|
||||
ReactCommon: b8487da74723562d7368dab27135fd182f00a91c
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af
|
||||
RNCAsyncStorage: c913ede1fa163a71cea118ed4670bbaaa4b511bb
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 0400a6d0c94186d1120c3cbd97b23abc022187a9
|
||||
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
|
||||
RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39
|
||||
RNGoogleSignin: 22e468a9474dbcb8618d8847205ad4f0b2575d13
|
||||
RNScreens: 68fd1060f57dd1023880bf4c05d74784b5392789
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
|
||||
RNWatch: dae6c858a2051dbdcfb00b9a86cf4d90400263b4
|
||||
RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0
|
||||
RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7
|
||||
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
|
||||
RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82
|
||||
RNWatch: fd30ca40a5b5ef58dcbc195638e68219bc455236
|
||||
Yoga: 7f5ad94937ba3fc58c151ad1b7bbada2c275b28e
|
||||
|
||||
PODFILE CHECKSUM: e3579df5272b8b697c9fdc0e55aa0845b189c4dd
|
||||
|
||||
2264
lang/main-sq.json
2264
lang/main-sq.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -250,13 +250,14 @@
|
||||
"dialog": {
|
||||
"Back": "Back",
|
||||
"Cancel": "Cancel",
|
||||
"IamHost": "I am the host",
|
||||
"IamHost": "Log-in",
|
||||
"Ok": "OK",
|
||||
"Remove": "Remove",
|
||||
"Share": "Share",
|
||||
"Submit": "Submit",
|
||||
"WaitForHostMsg": "The conference has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitingForHostTitle": "Waiting for the host ...",
|
||||
"WaitForHostMsg": "The conference has not yet started because no moderators have yet arrived. If you'd like to become a moderator please log-in. Otherwise, please wait.",
|
||||
"WaitingForHostButton": "Wait for moderator",
|
||||
"WaitingForHostTitle": "Waiting for a moderator...",
|
||||
"Yes": "Yes",
|
||||
"accessibilityLabel": {
|
||||
"Cancel": "Cancel (leave dialog)",
|
||||
@@ -269,6 +270,8 @@
|
||||
"addMeetingNote": "Add a note about this meeting",
|
||||
"addOptionalNote": "Add a note (optional):",
|
||||
"allow": "Allow",
|
||||
"allowToggleCameraDialog": "Do you allow {{initiatorName}} to toggle your camera facing mode?",
|
||||
"allowToggleCameraTitle": "Allow toggle camera?",
|
||||
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
|
||||
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
|
||||
"applicationWindow": "Application window",
|
||||
@@ -329,6 +332,7 @@
|
||||
"lockRoom": "Add meeting $t(lockRoomPassword)",
|
||||
"lockTitle": "Lock failed",
|
||||
"login": "Login",
|
||||
"loginQuestion": "Are you sure you want to login and leave the conference?",
|
||||
"logoutQuestion": "Are you sure you want to logout and leave the conference?",
|
||||
"logoutTitle": "Logout",
|
||||
"maxUsersLimitReached": "The limit for maximum number of participants has been reached. The conference is full. Please contact the meeting owner or try again later!",
|
||||
@@ -409,6 +413,7 @@
|
||||
"sendPrivateMessageTitle": "Send privately?",
|
||||
"serviceUnavailable": "Service unavailable",
|
||||
"sessTerminated": "Call terminated",
|
||||
"sessTerminatedReason": "The meeting has been terminated",
|
||||
"sessionRestarted": "Call restarted because of a connection issue.",
|
||||
"shareAudio": "Continue",
|
||||
"shareAudioTitle": "How to share audio",
|
||||
@@ -547,6 +552,7 @@
|
||||
"password": "$t(lockRoomPasswordUppercase): ",
|
||||
"reachedLimit": "You have reached the limit of your plan.",
|
||||
"sip": "SIP address",
|
||||
"sipAudioOnly": "SIP audio only address",
|
||||
"title": "Share",
|
||||
"tooltip": "Share link and dial-in info for this meeting",
|
||||
"upgradeOptions": "Please check the upgrade options on"
|
||||
@@ -653,13 +659,13 @@
|
||||
"knockingParticipantList": "Knocking participant list",
|
||||
"lobbyChatStartedNotification": "{{moderator}} started a lobby chat with {{attendee}}",
|
||||
"lobbyChatStartedTitle": "{{moderator}} has started a lobby chat with you.",
|
||||
"lobbyClosed": "The lobby room has been closed.",
|
||||
"nameField": "Enter your name",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} has been rejected to join by {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} has been allowed to join by {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "Lobby has been disabled by {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Lobby has been enabled by {{originParticipantName}}",
|
||||
"notificationTitle": "Lobby",
|
||||
"passwordField": "Enter meeting password",
|
||||
"passwordJoinButton": "Join",
|
||||
"reject": "Reject",
|
||||
"rejectAll": "Reject all",
|
||||
@@ -1078,6 +1084,7 @@
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Warning",
|
||||
"alertURLText": "The entered server URL is invalid",
|
||||
"apply": "Apply",
|
||||
"buildInfoSection": "Build Information",
|
||||
"conferenceSection": "Conference",
|
||||
"disableCallIntegration": "Disable native call integration",
|
||||
@@ -1088,6 +1095,7 @@
|
||||
"displayNamePlaceholderText": "Eg: John Doe",
|
||||
"email": "Email",
|
||||
"emailPlaceholderText": "email@example.com",
|
||||
"gavatarMessage": "If your email is associated with a Gravatar account, we will use it to display your profile picture.",
|
||||
"goTo": "Go to",
|
||||
"header": "Settings",
|
||||
"help": "Help",
|
||||
@@ -1270,8 +1278,8 @@
|
||||
"linkToSalesforce": "Link to Salesforce",
|
||||
"lobbyButtonDisable": "Disable lobby mode",
|
||||
"lobbyButtonEnable": "Enable lobby mode",
|
||||
"login": "Login",
|
||||
"logout": "Logout",
|
||||
"login": "Log-in",
|
||||
"logout": "Log-out",
|
||||
"lowerYourHand": "Lower your hand",
|
||||
"moreActions": "More actions",
|
||||
"moreOptions": "More options",
|
||||
|
||||
@@ -50,8 +50,8 @@ import {
|
||||
} from '../../react/features/base/participants/functions';
|
||||
import { updateSettings } from '../../react/features/base/settings/actions';
|
||||
import { getDisplayName } from '../../react/features/base/settings/functions.web';
|
||||
import { toggleCamera } from '../../react/features/base/tracks/actions.any';
|
||||
import { isToggleCameraEnabled } from '../../react/features/base/tracks/functions';
|
||||
import { setCameraFacingMode } from '../../react/features/base/tracks/actions.web';
|
||||
import { CAMERA_FACING_MODE_MESSAGE } from '../../react/features/base/tracks/constants';
|
||||
import {
|
||||
autoAssignToBreakoutRooms,
|
||||
closeBreakoutRoom,
|
||||
@@ -395,12 +395,8 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('film.strip.resize'));
|
||||
APP.store.dispatch(resizeFilmStrip(options.width));
|
||||
},
|
||||
'toggle-camera': () => {
|
||||
if (!isToggleCameraEnabled(APP.store.getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(toggleCamera());
|
||||
'toggle-camera': facingMode => {
|
||||
APP.store.dispatch(setCameraFacingMode(facingMode));
|
||||
},
|
||||
'toggle-camera-mirror': () => {
|
||||
const state = APP.store.getState();
|
||||
@@ -529,6 +525,18 @@ function initCommands() {
|
||||
logger.error('Failed sending endpoint text message', err);
|
||||
}
|
||||
},
|
||||
'send-camera-facing-mode-message': (to, facingMode) => {
|
||||
if (!to) {
|
||||
logger.warn('Participant id not set');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.conference.sendEndpointMessage(to, {
|
||||
name: CAMERA_FACING_MODE_MESSAGE,
|
||||
facingMode
|
||||
});
|
||||
},
|
||||
'overwrite-names': participantList => {
|
||||
logger.debug('Overwrite names command received');
|
||||
|
||||
@@ -1133,7 +1141,11 @@ class API {
|
||||
*/
|
||||
_sendEvent(event = {}) {
|
||||
if (this._enabled) {
|
||||
transport.sendEvent(event);
|
||||
try {
|
||||
transport.sendEvent(event);
|
||||
} catch (error) {
|
||||
logger.error('Failed to send and IFrame API event', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1467,11 +1479,43 @@ class API {
|
||||
* @param {Array<string>} args - Array of strings composing the log message.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyLog(logLevel, args) {
|
||||
notifyLog(logLevel, args = []) {
|
||||
if (!Array.isArray(args)) {
|
||||
logger.error('notifyLog received wrong argument types!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Trying to convert arguments to strings. Otherwise in order to send the event the arguments will be formatted
|
||||
// with JSON.stringify which can throw an error because of circular objects and we will lose the whole log.
|
||||
const formattedArguments = [];
|
||||
|
||||
args.forEach(arg => {
|
||||
let formattedArgument = '';
|
||||
|
||||
if (arg instanceof Error) {
|
||||
formattedArgument += `${arg.toString()}: ${arg.stack}`;
|
||||
} else if (typeof arg === 'object') {
|
||||
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
|
||||
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
|
||||
// do something better for parsing the arguments. But since this option for strigify is part of the
|
||||
// public interface and I think it could be useful in some cases I will it for now.
|
||||
try {
|
||||
formattedArgument += JSON.stringify(arg);
|
||||
} catch (error) {
|
||||
formattedArgument += arg;
|
||||
}
|
||||
} else {
|
||||
formattedArgument += arg;
|
||||
}
|
||||
|
||||
formattedArguments.push(formattedArgument);
|
||||
});
|
||||
|
||||
this._sendEvent({
|
||||
name: 'log',
|
||||
logLevel,
|
||||
args
|
||||
args: formattedArguments
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
3
modules/API/external/external_api.js
vendored
3
modules/API/external/external_api.js
vendored
@@ -54,6 +54,7 @@ const commands = {
|
||||
removeBreakoutRoom: 'remove-breakout-room',
|
||||
resizeFilmStrip: 'resize-film-strip',
|
||||
resizeLargeVideo: 'resize-large-video',
|
||||
sendCameraFacingMode: 'send-camera-facing-mode-message',
|
||||
sendChatMessage: 'send-chat-message',
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendParticipantToRoom: 'send-participant-to-room',
|
||||
@@ -112,6 +113,7 @@ const events = {
|
||||
'data-channel-opened': 'dataChannelOpened',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
'dominant-speaker-changed': 'dominantSpeakerChanged',
|
||||
'email-change': 'emailChange',
|
||||
'error-occurred': 'errorOccurred',
|
||||
'endpoint-text-message-received': 'endpointTextMessageReceived',
|
||||
@@ -153,7 +155,6 @@ const events = {
|
||||
'video-mute-status-changed': 'videoMuteStatusChanged',
|
||||
'video-quality-changed': 'videoQualityChanged',
|
||||
'screen-sharing-status-changed': 'screenSharingStatusChanged',
|
||||
'dominant-speaker-changed': 'dominantSpeakerChanged',
|
||||
'subject-change': 'subjectChange',
|
||||
'suspend-detected': 'suspendDetected',
|
||||
'tile-view-changed': 'tileViewChanged',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* global APP, interfaceConfig */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import Logger from '@jitsi/logger';
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
@@ -23,6 +24,8 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
|
||||
// Corresponds to animation duration from the animatedFadeIn and animatedFadeOut CSS classes.
|
||||
const FADE_DURATION_MS = 300;
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Returns an array of the video dimensions, so that it keeps it's aspect
|
||||
* ratio and fits available area with it's larger dimension. This method
|
||||
@@ -489,7 +492,9 @@ export class VideoContainer extends LargeContainer {
|
||||
}
|
||||
|
||||
if (this.video) {
|
||||
stream.attach(this.video);
|
||||
stream.attach(this.video).catch(error => {
|
||||
logger.error(`Attaching the remote track ${stream} has failed with `, error);
|
||||
});
|
||||
|
||||
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
|
||||
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
|
||||
|
||||
@@ -23,11 +23,8 @@ const VideoLayout = {
|
||||
/**
|
||||
* Handler for local flip X changed event.
|
||||
*/
|
||||
onLocalFlipXChanged() {
|
||||
onLocalFlipXChanged(localFlipX) {
|
||||
if (largeVideo) {
|
||||
const { store } = APP;
|
||||
const { localFlipX } = store.getState()['features/base/settings'];
|
||||
|
||||
largeVideo.onLocalFlipXChange(localFlipX);
|
||||
}
|
||||
},
|
||||
|
||||
1844
package-lock.json
generated
1844
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
50
package.json
50
package.json
@@ -23,19 +23,19 @@
|
||||
"@giphy/react-components": "6.8.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
|
||||
"@jitsi/js-utils": "2.1.2",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
"@microsoft/microsoft-graph-client": "3.0.1",
|
||||
"@mui/material": "5.12.1",
|
||||
"@mui/styles": "5.12.0",
|
||||
"@react-native-async-storage/async-storage": "1.17.3",
|
||||
"@react-native-async-storage/async-storage": "1.19.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "9.0.2",
|
||||
"@react-native-community/netinfo": "9.4.1",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.0.1",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
"@react-navigation/material-top-tabs": "6.6.3",
|
||||
@@ -58,14 +58,14 @@
|
||||
"i18n-iso-countries": "6.8.0",
|
||||
"i18next": "17.0.6",
|
||||
"i18next-browser-languagedetector": "3.0.1",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"i18next-http-backend": "^2.2.1",
|
||||
"image-capture": "0.4.0",
|
||||
"jquery": "3.6.1",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"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/v1674.0.0+648d0ddc/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -82,31 +82,31 @@
|
||||
"react-native": "0.69.11",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-callstats": "3.73.22",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "5.1.2",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-safe-area-context": "4.6.4",
|
||||
"react-native-screens": "3.22.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-video": "6.0.0-alpha.7",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-native-webview": "13.5.1",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-window": "1.8.6",
|
||||
|
||||
@@ -26,10 +26,10 @@ Pod::Spec.new do |s|
|
||||
s.script_phase = {
|
||||
:name => 'Copy Sound Files',
|
||||
:script => '
|
||||
SOURCE_PATH="${PODS_TARGET_SRCROOT}/sounds"
|
||||
TARGET_PATH="$(dirname ${CONFIGURATION_BUILD_DIR})"
|
||||
PROJECT_NAME=$(basename $(dirname $(dirname ${PROJECT_DIR}))).app
|
||||
cp -R "${PODS_TARGET_SRCROOT}/sounds/" "${TARGET_PATH}/${PROJECT_NAME}"
|
||||
SOURCE_PATH="${PODS_TARGET_SRCROOT}/sounds/"
|
||||
TARGET_PATH=$(dirname "${CONFIGURATION_BUILD_DIR}")
|
||||
PROJECT_NAME=$(basename $(dirname $(dirname "${PROJECT_DIR}"))).app
|
||||
cp -R "${SOURCE_PATH}" "${TARGET_PATH}/${PROJECT_NAME}"
|
||||
',
|
||||
}
|
||||
end
|
||||
|
||||
1336
react-native-sdk/package-lock.json
generated
1336
react-native-sdk/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,9 +11,8 @@
|
||||
"url": "git+https://github.com/jitsi/jitsi-meet.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/js-utils": "2.0.5",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@react-navigation/bottom-tabs": "6.5.8",
|
||||
"@react-navigation/elements": "1.3.18",
|
||||
@@ -25,11 +24,10 @@
|
||||
"grapheme-splitter": "1.0.4",
|
||||
"i18n-iso-countries": "6.8.0",
|
||||
"i18next": "17.0.6",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"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/v1670.0.0+10ebc843/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1681.0.0+6cd397fa/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -39,12 +37,12 @@
|
||||
"react-emoji-render": "1.2.4",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native-callstats": "3.73.7",
|
||||
"react-native-callstats": "3.73.22",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-svg-transformer": "1.0.0",
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-native-svg-transformer": "1.1.0",
|
||||
"react-native-tab-view": "3.5.2",
|
||||
"react-native-url-polyfill": "2.0.0",
|
||||
"react-native-youtube-iframe": "2.3.0",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.4.1",
|
||||
@@ -56,34 +54,34 @@
|
||||
"peerDependencies": {
|
||||
"@amplitude/react-native": "2.7.0",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@react-native-async-storage/async-storage": "1.18.2",
|
||||
"@react-native-async-storage/async-storage": "1.19.3",
|
||||
"@react-native-community/clipboard": "1.5.1",
|
||||
"@react-native-community/netinfo": "7.1.7",
|
||||
"@react-native-community/slider": "4.1.12",
|
||||
"@react-native-google-signin/google-signin": "7.0.4",
|
||||
"@react-native-community/netinfo": "9.4.1",
|
||||
"@react-native-community/slider": "4.4.3",
|
||||
"@react-native-google-signin/google-signin": "10.0.1",
|
||||
"react-native": "*",
|
||||
"react": "*",
|
||||
"react-native-background-timer": "2.4.1",
|
||||
"react-native-calendar-events": "2.2.0",
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "8.4.8",
|
||||
"react-native-get-random-values": "1.7.2",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-gesture-handler": "2.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-pager-view": "5.4.9",
|
||||
"react-native-paper": "4.11.1",
|
||||
"react-native-performance": "2.1.0",
|
||||
"react-native-orientation-locker": "https://git@github.com/wonday/react-native-orientation-locker#f483520ea6b64b97002374a9e9f053a5299a062a",
|
||||
"react-native-safe-area-context": "4.4.1",
|
||||
"react-native-screens": "3.22.0",
|
||||
"react-native-sound": "0.11.1",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-orientation-locker": "1.5.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-screens": "3.24.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
"react-native-svg": "12.4.3",
|
||||
"react-native-video": "https://git@github.com/react-native-video/react-native-video#7c48ae7c8544b2b537fb60194e9620b9fcceae52",
|
||||
"react-native-watch-connectivity": "1.0.11",
|
||||
"react-native-webrtc": "111.0.1",
|
||||
"react-native-webview": "11.15.1"
|
||||
"react-native-svg": "13.13.0",
|
||||
"react-native-video": "6.0.0-alpha.7",
|
||||
"react-native-watch-connectivity": "1.1.0",
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "13.5.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
@@ -98,4 +96,4 @@
|
||||
"keywords": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
react-native-sdk/prepare_sdk.js
vendored
14
react-native-sdk/prepare_sdk.js
vendored
@@ -58,11 +58,25 @@ function copyFolderRecursiveSync(source, target) {
|
||||
* Merges the dependency versions from the root package.json with the dependencies of the SDK package.json.
|
||||
*/
|
||||
function mergeDependencyVersions() {
|
||||
|
||||
// Updates SDK dependencies to match project dependencies.
|
||||
for (const key in SDKPackageJSON.dependencies) {
|
||||
if (SDKPackageJSON.dependencies.hasOwnProperty(key)) {
|
||||
SDKPackageJSON.dependencies[key] = packageJSON.dependencies[key] || packageJSON.devDependencies[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Updates SDK peer dependencies.
|
||||
for (const key in packageJSON.dependencies) {
|
||||
if (SDKPackageJSON.peerDependencies.hasOwnProperty(key)) {
|
||||
|
||||
// Updates all peer dependencies except react and react-native.
|
||||
if (key !== 'react' && key !== 'react-native') {
|
||||
SDKPackageJSON.peerDependencies[key] = packageJSON.dependencies[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const data = JSON.stringify(SDKPackageJSON, null, 4);
|
||||
|
||||
fs.writeFileSync('package.json', data);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Amplitude } from '@amplitude/react-native';
|
||||
import DefaultPreference from 'react-native-default-preference';
|
||||
import DeviceInfo from 'react-native-device-info';
|
||||
import { getUniqueId } from 'react-native-device-info';
|
||||
|
||||
import logger from '../../logger';
|
||||
|
||||
|
||||
/**
|
||||
* Custom logic for setting the correct device id.
|
||||
@@ -14,11 +17,17 @@ export async function fixDeviceID(amplitude: Amplitude) {
|
||||
const current = await DefaultPreference.get('amplitudeDeviceId');
|
||||
|
||||
if (current) {
|
||||
amplitude.setDeviceId(current);
|
||||
await amplitude.setDeviceId(current);
|
||||
} else {
|
||||
const uid = DeviceInfo.getUniqueId();
|
||||
const uid = await getUniqueId();
|
||||
|
||||
amplitude.setDeviceId(uid);
|
||||
DefaultPreference.set('amplitudeDeviceId', uid);
|
||||
if (!uid) {
|
||||
logger.warn('Device ID is not set!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await amplitude.setDeviceId(uid as string);
|
||||
await DefaultPreference.set('amplitudeDeviceId', uid as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { getTokenAuthUrl } from '../authentication/functions';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { isSupportedBrowser } from '../base/environment/environment';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import Conference from '../conference/components/web/Conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking/functions';
|
||||
@@ -48,9 +49,9 @@ function _getWebConferenceRoute(state: IReduxState) {
|
||||
|
||||
// if we have auto redirect enabled, and we have previously logged in successfully
|
||||
// let's redirect to the auth url to get the token and login again
|
||||
if (config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/authentication'].tokenAuthUrlSuccessful
|
||||
&& !state['features/base/jwt'].jwt && room) {
|
||||
if (!browser.isElectron() && config.tokenAuthUrl && config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/authentication'].tokenAuthUrlSuccessful
|
||||
&& !state['features/base/jwt'].jwt && room) {
|
||||
route.href = getTokenAuthUrl(config, room);
|
||||
|
||||
return Promise.resolve(route);
|
||||
|
||||
@@ -4,6 +4,8 @@ import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
|
||||
import {
|
||||
LOGIN,
|
||||
LOGOUT,
|
||||
SET_TOKEN_AUTH_URL_SUCCESS,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
@@ -137,6 +139,32 @@ export function hideLoginDialog() {
|
||||
return hideDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LOGIN
|
||||
* }}
|
||||
*/
|
||||
export function login() {
|
||||
return {
|
||||
type: LOGIN
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LOGOUT
|
||||
* }}
|
||||
*/
|
||||
export function logout() {
|
||||
return {
|
||||
type: LOGOUT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link WaitForOnwerDialog}.
|
||||
*
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Linking, Platform } from 'react-native';
|
||||
|
||||
import { appNavigate } from '../app/actions.native';
|
||||
import { IStore } from '../app/types';
|
||||
import { conferenceLeft } from '../base/conference/actions';
|
||||
import { connectionFailed } from '../base/connection/actions.native';
|
||||
import { set } from '../base/redux/functions';
|
||||
import { appendURLHashParam } from '../base/util/uri';
|
||||
|
||||
import { CANCEL_LOGIN } from './actionTypes';
|
||||
import { stopWaitForOwner } from './actions.any';
|
||||
@@ -51,7 +54,7 @@ export function cancelWaitForOwner() {
|
||||
// recoverable by the feature room-lock and, consequently,
|
||||
// recoverable-aware features such as mobile's external-api did not
|
||||
// deliver the CONFERENCE_FAILED to the SDK clients/consumers. Since the
|
||||
// app/user is going to nativate to WelcomePage, the SDK
|
||||
// app/user is going to navigate to WelcomePage, the SDK
|
||||
// clients/consumers need an event.
|
||||
const { authRequired } = getState()['features/base/conference'];
|
||||
|
||||
@@ -65,7 +68,7 @@ export function cancelWaitForOwner() {
|
||||
};
|
||||
}
|
||||
|
||||
/** .
|
||||
/**
|
||||
* Redirect to the default location (e.g. Welcome page).
|
||||
*
|
||||
* @returns {Function}
|
||||
@@ -73,3 +76,21 @@ export function cancelWaitForOwner() {
|
||||
export function redirectToDefaultLocation() {
|
||||
return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens token auth URL page.
|
||||
*
|
||||
* @param {string} tokenAuthServiceUrl - Authentication service URL.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function openTokenAuthUrl(tokenAuthServiceUrl: string) {
|
||||
let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true');
|
||||
|
||||
// Append ios=true or android=true to the token URL.
|
||||
url = appendURLHashParam(url, Platform.OS, 'true');
|
||||
|
||||
return () => {
|
||||
Linking.openURL(url);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions.web';
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { appendURLHashParam } from '../base/util/uri';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
LOGIN,
|
||||
LOGOUT
|
||||
} from './actionTypes';
|
||||
import { CANCEL_LOGIN } from './actionTypes';
|
||||
import LoginQuestionDialog from './components/web/LoginQuestionDialog';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -38,7 +38,7 @@ export function cancelWaitForOwner() {
|
||||
};
|
||||
}
|
||||
|
||||
/** .
|
||||
/**
|
||||
* Redirect to the default location (e.g. Welcome page).
|
||||
*
|
||||
* @returns {Function}
|
||||
@@ -48,27 +48,36 @@ export function redirectToDefaultLocation() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Login.
|
||||
* Opens token auth URL page.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LOGIN
|
||||
* }}
|
||||
* @param {string} tokenAuthServiceUrl - Authentication service URL.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function login() {
|
||||
return {
|
||||
type: LOGIN
|
||||
};
|
||||
}
|
||||
export function openTokenAuthUrl(tokenAuthServiceUrl: string): any {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const redirect = () => {
|
||||
// We have already shown the prejoin screen, no need to show it again after obtaining the token.
|
||||
let url = appendURLHashParam(tokenAuthServiceUrl, 'skipPrejoin', 'true');
|
||||
|
||||
/**
|
||||
* Logout.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LOGOUT
|
||||
* }}
|
||||
*/
|
||||
export function logout() {
|
||||
return {
|
||||
type: LOGOUT
|
||||
if (browser.isElectron()) {
|
||||
url = appendURLHashParam(url, 'electron', 'true');
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
window.location.href = url;
|
||||
}
|
||||
};
|
||||
|
||||
// Show warning for leaving conference only when in a conference.
|
||||
if (!browser.isElectron() && getState()['features/base/conference'].conference) {
|
||||
dispatch(openDialog(LoginQuestionDialog, {
|
||||
handler: () => {
|
||||
// Give time for the dialog to close.
|
||||
setTimeout(() => redirect, 500);
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
redirect();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../../app/types';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import ConfirmDialog from '../../../base/dialog/components/native/ConfirmDialog';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { cancelWaitForOwner, openLoginDialog } from '../../actions.native';
|
||||
import { cancelWaitForOwner, login } from '../../actions.native';
|
||||
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Whether to show alternative cancel button text.
|
||||
*/
|
||||
_alternativeCancelText?: boolean;
|
||||
|
||||
/**
|
||||
* Redux store dispatch function.
|
||||
*/
|
||||
@@ -52,7 +58,7 @@ class WaitForOwnerDialog extends Component<IProps> {
|
||||
render() {
|
||||
return (
|
||||
<ConfirmDialog
|
||||
cancelLabel = 'dialog.Cancel'
|
||||
cancelLabel = { this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }
|
||||
confirmLabel = 'dialog.IamHost'
|
||||
descriptionKey = 'dialog.WaitForHostMsg'
|
||||
onCancel = { this._onCancel }
|
||||
@@ -77,8 +83,24 @@ class WaitForOwnerDialog extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLogin() {
|
||||
this.props.dispatch(openLoginDialog());
|
||||
this.props.dispatch(login());
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(WaitForOwnerDialog));
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated
|
||||
* {@code WaitForOwnerDialog}'s props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_alternativeCancelText: membersOnly && lobbyWaitingForHost
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
|
||||
/**
|
||||
* The type of {@link LoginQuestionDialog}'s React {@code Component} props.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The handler.
|
||||
*/
|
||||
handler: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the dialog that warns the user that the login will leave the conference.
|
||||
*
|
||||
* @param {Object} props - The props of the component.
|
||||
* @returns {React$Element}.
|
||||
*/
|
||||
const LoginQuestionDialog = ({ handler }: IProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{ translationKey: 'dialog.Yes' }}
|
||||
onSubmit = { handler }
|
||||
titleKey = { t('dialog.login') }>
|
||||
<div>
|
||||
{ t('dialog.loginQuestion') }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginQuestionDialog;
|
||||
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../../app/types';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import { cancelWaitForOwner, login } from '../../actions.web';
|
||||
@@ -12,6 +12,11 @@ import { cancelWaitForOwner, login } from '../../actions.web';
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether to show alternative cancel button text.
|
||||
*/
|
||||
_alternativeCancelText?: boolean;
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
@@ -71,6 +76,8 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ translationKey:
|
||||
this.props._alternativeCancelText ? 'dialog.WaitingForHostButton' : 'dialog.Cancel' }}
|
||||
disableBackdropClose = { true }
|
||||
hideCloseButton = { true }
|
||||
ok = {{ translationKey: 'dialog.IamHost' }}
|
||||
@@ -85,4 +92,20 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(WaitForOwnerDialog));
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated
|
||||
* {@code WaitForOwnerDialog}'s props.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_alternativeCancelText: membersOnly && lobbyWaitingForHost
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));
|
||||
|
||||
@@ -27,7 +27,7 @@ export const isTokenAuthEnabled = (config: IConfig) =>
|
||||
* <tt>undefined</tt> if the pattern stored in config is not a string and the URL can not be
|
||||
* constructed.
|
||||
*/
|
||||
export const getTokenAuthUrl = (config: IConfig, roomName: string) => {
|
||||
export const getTokenAuthUrl = (config: IConfig, roomName: string | undefined) => {
|
||||
|
||||
const url = config.tokenAuthUrl;
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from '../base/conference/actionTypes';
|
||||
import { isRoomValid } from '../base/conference/functions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../base/connection/actionTypes';
|
||||
import { hangup } from '../base/connection/actions';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
import { isDialogOpen } from '../base/dialog/functions';
|
||||
import {
|
||||
@@ -16,8 +15,6 @@ import {
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { getBackendSafeRoomName } from '../base/util/uri';
|
||||
import { showErrorNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications/constants';
|
||||
import { openLogoutDialog } from '../settings/actions';
|
||||
|
||||
import {
|
||||
@@ -31,6 +28,7 @@ import {
|
||||
import {
|
||||
hideLoginDialog,
|
||||
openLoginDialog,
|
||||
openTokenAuthUrl,
|
||||
openWaitForOwnerDialog,
|
||||
redirectToDefaultLocation,
|
||||
setTokenAuthUrlSuccess,
|
||||
@@ -39,6 +37,8 @@ import {
|
||||
} from './actions';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
|
||||
/**
|
||||
* Middleware that captures connection or conference failed errors and controls
|
||||
@@ -163,33 +163,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case LOGOUT: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
if (!conference) {
|
||||
break;
|
||||
}
|
||||
|
||||
dispatch(openLogoutDialog(() => {
|
||||
const logoutUrl = config.tokenLogoutUrl;
|
||||
|
||||
if (isTokenAuthEnabled(config)
|
||||
&& config.tokenAuthUrlAutoRedirect
|
||||
&& state['features/base/jwt'].jwt) {
|
||||
// user is logging out remove auto redirect indication
|
||||
dispatch(setTokenAuthUrlSuccess(false));
|
||||
}
|
||||
|
||||
if (logoutUrl) {
|
||||
window.location.href = logoutUrl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
conference.room.moderator.logout(() => dispatch(hangup(true)));
|
||||
}));
|
||||
_handleLogout(store);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -282,31 +256,45 @@ function _handleLogin({ dispatch, getState }: IStore) {
|
||||
const config = state['features/base/config'];
|
||||
const room = getBackendSafeRoomName(state['features/base/conference'].room);
|
||||
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
if (typeof APP === 'undefined') {
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.tokenAuthUnsupported',
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
if (!room) {
|
||||
logger.warn('Cannot handle login, room is undefined!');
|
||||
|
||||
dispatch(redirectToDefaultLocation());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: This method will not preserve the other URL params that were originally passed.
|
||||
const tokenAuthServiceUrl = getTokenAuthUrl(config, room);
|
||||
|
||||
if (tokenAuthServiceUrl) {
|
||||
// we have already shown the prejoin screen, so no need to show it again after obtaining the token
|
||||
window.location.href = `${tokenAuthServiceUrl}${
|
||||
tokenAuthServiceUrl.includes('#') ? '&' : '#'}skipPrejoin=true`;
|
||||
}
|
||||
} else {
|
||||
dispatch(openLoginDialog());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
dispatch(openLoginDialog());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: This method will not preserve the other URL params that were originally passed.
|
||||
const tokenAuthServiceUrl = getTokenAuthUrl(config, room);
|
||||
|
||||
if (!tokenAuthServiceUrl) {
|
||||
logger.warn('Cannot handle login, token service URL is not set');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openTokenAuthUrl(tokenAuthServiceUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles logout challenge. Opens logout dialog and hangs up the conference.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {string} logoutUrl - The url for logging out.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleLogout({ dispatch, getState }: IStore) {
|
||||
const state = getState();
|
||||
const { conference } = state['features/base/conference'];
|
||||
|
||||
if (!conference) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(openLogoutDialog());
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEve
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { endpointMessageReceived } from '../../subtitles/actions.any';
|
||||
import { setIAmVisitor } from '../../visitors/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { getReplaceParticipant } from '../config/functions';
|
||||
import { hangup } from '../connection/actions';
|
||||
import { connect, disconnect, hangup } from '../connection/actions';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection/constants';
|
||||
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setAudioUnmutePermissions, setVideoMuted, setVideoUnmutePermissions } from '../media/actions';
|
||||
@@ -73,6 +75,7 @@ import {
|
||||
getConferenceOptions,
|
||||
getConferenceState,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
sendLocalParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -983,3 +986,41 @@ export function setAssumedBandwidthBps(assumedBandwidthBps: number) {
|
||||
assumedBandwidthBps
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects to a new visitor node.
|
||||
*
|
||||
* @param {string | undefined} vnode - The vnode to use or undefined if moving back to the main room.
|
||||
* @param {string} focusJid - The focus jid to use.
|
||||
* @param {string} username - The username to use.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function redirect(vnode: string, focusJid: string, username: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const { conference, joining } = getState()['features/base/conference'];
|
||||
|
||||
const newConfig = getVisitorOptions(getState, vnode, focusJid, username);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference || joining)))
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,9 +28,10 @@ export const EMAIL_COMMAND = 'email';
|
||||
*/
|
||||
export const JITSI_CONFERENCE_URL_KEY = Symbol('url');
|
||||
|
||||
export const TRIGGER_READY_TO_CLOSE_REASONS = [
|
||||
'The meeting has been terminated'
|
||||
];
|
||||
export const TRIGGER_READY_TO_CLOSE_REASONS = {
|
||||
'dialog.sessTerminatedReason': 'The meeting has been terminated',
|
||||
'lobby.lobbyClosed': 'Lobby room closed.'
|
||||
};
|
||||
|
||||
/**
|
||||
* Conference leave reasons.
|
||||
@@ -39,8 +40,3 @@ export const CONFERENCE_LEAVE_REASONS = {
|
||||
SWITCH_ROOM: 'switch_room',
|
||||
UNRECOVERABLE_ERROR: 'unrecoverable_error'
|
||||
};
|
||||
|
||||
/**
|
||||
* Timeout for properly leaving the conference if it was destroyed.
|
||||
*/
|
||||
export const CONFERENCE_DESTROYED_LEAVE_TIMEOUT = 10000;
|
||||
|
||||
@@ -284,12 +284,12 @@ export function restoreConferenceOptions(stateful: IStateful) {
|
||||
* Override the global config (that is, window.config) with XMPP configuration required to join as a visitor.
|
||||
*
|
||||
* @param {IStateful} stateful - The redux store state.
|
||||
* @param {Array<string>} params - The received parameters.
|
||||
* @param {string|undefined} vnode - The received parameters.
|
||||
* @param {string} focusJid - The received parameters.
|
||||
* @param {string|undefined} username - The received parameters.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function getVisitorOptions(stateful: IStateful, params: Array<string>) {
|
||||
const [ vnode, focusJid, username ] = params;
|
||||
|
||||
export function getVisitorOptions(stateful: IStateful, vnode: string, focusJid: string, username: string) {
|
||||
const config = toState(stateful)['features/base/config'];
|
||||
|
||||
if (!config?.hosts) {
|
||||
|
||||
@@ -14,12 +14,10 @@ import { reloadNow } from '../../app/actions';
|
||||
import { IStore } from '../../app/types';
|
||||
import { removeLobbyChatParticipant } from '../../chat/actions.any';
|
||||
import { openDisplayNamePrompt } from '../../display-name/actions';
|
||||
import { readyToClose } from '../../mobile/external-api/actions';
|
||||
import { showErrorNotification, showWarningNotification } from '../../notifications/actions';
|
||||
import { showErrorNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { stopLocalVideoRecording } from '../../recording/actions.any';
|
||||
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager';
|
||||
import { setIAmVisitor } from '../../visitors/actions';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { overwriteConfig } from '../config/actions';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED } from '../connection/actionTypes';
|
||||
@@ -35,7 +33,6 @@ import {
|
||||
} from '../participants/functions';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { TRACK_ADDED, TRACK_REMOVED } from '../tracks/actionTypes';
|
||||
import { destroyLocalTracks } from '../tracks/actions.any';
|
||||
import { getLocalTracks } from '../tracks/functions.any';
|
||||
|
||||
import {
|
||||
@@ -52,24 +49,17 @@ import {
|
||||
import {
|
||||
authStatusChanged,
|
||||
conferenceFailed,
|
||||
conferenceWillInit,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
leaveConference,
|
||||
setLocalSubject,
|
||||
setSubject
|
||||
} from './actions';
|
||||
import {
|
||||
CONFERENCE_DESTROYED_LEAVE_TIMEOUT,
|
||||
CONFERENCE_LEAVE_REASONS,
|
||||
TRIGGER_READY_TO_CLOSE_REASONS
|
||||
} from './constants';
|
||||
import { CONFERENCE_LEAVE_REASONS } from './constants';
|
||||
import {
|
||||
_addLocalTracksToConference,
|
||||
_removeLocalTracksFromConference,
|
||||
forEachConference,
|
||||
getCurrentConference,
|
||||
getVisitorOptions,
|
||||
restoreConferenceOptions
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
@@ -153,25 +143,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
|
||||
// Handle specific failure reasons.
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
const [ reason ] = error.params;
|
||||
|
||||
dispatch(showWarningNotification({
|
||||
description: reason,
|
||||
titleKey: 'dialog.sessTerminated'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
||||
if (TRIGGER_READY_TO_CLOSE_REASONS.includes(reason)) {
|
||||
if (typeof APP === 'undefined') {
|
||||
dispatch(readyToClose());
|
||||
} else {
|
||||
APP.API.notifyReadyToClose();
|
||||
}
|
||||
setTimeout(() => dispatch(leaveConference()), CONFERENCE_DESTROYED_LEAVE_TIMEOUT);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.CONFERENCE_RESTARTED: {
|
||||
if (enableForcedReload) {
|
||||
dispatch(showErrorNotification({
|
||||
@@ -227,34 +198,6 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
const newConfig = getVisitorOptions(getState, error.params);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
break;
|
||||
}
|
||||
|
||||
const [ vnode ] = error.params;
|
||||
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(() => dispatch(conferenceWillLeave(conference)))
|
||||
.then(() => dispatch(disconnect()))
|
||||
.then(() => dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => dispatch(destroyLocalTracks()))
|
||||
.then(() => dispatch(conferenceWillInit()))
|
||||
.then(() => dispatch(connect()))
|
||||
.then(() => {
|
||||
// FIXME: Workaround for the web version. To be removed once we get rid of conference.js
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.startConference([]);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
!error.recoverable
|
||||
|
||||
@@ -1 +1,36 @@
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { notifyConferenceFailed } from '../../conference/actions.native';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { CONFERENCE_FAILED } from './actionTypes';
|
||||
import { conferenceLeft } from './actions';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch } = store;
|
||||
const { error } = action;
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_FAILED: {
|
||||
if (error?.name !== JitsiConferenceErrors.CONFERENCE_DESTROYED) {
|
||||
break;
|
||||
}
|
||||
|
||||
const [ reason ] = error.params;
|
||||
|
||||
const reasonKey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
|
||||
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
|
||||
];
|
||||
|
||||
dispatch(notifyConferenceFailed(reasonKey, () => {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import i18next from 'i18next';
|
||||
|
||||
import {
|
||||
setPrejoinPageVisibility,
|
||||
setSkipPrejoinOnReload
|
||||
} from '../../prejoin/actions.web';
|
||||
import { hangup } from '../connection/actions.web';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
@@ -12,7 +15,9 @@ import {
|
||||
CONFERENCE_LEFT,
|
||||
KICKED_OUT
|
||||
} from './actionTypes';
|
||||
import { TRIGGER_READY_TO_CLOSE_REASONS } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
let screenLock: WakeLockSentinel | undefined;
|
||||
@@ -108,6 +113,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
dispatch(setSkipPrejoinOnReload(true));
|
||||
}
|
||||
|
||||
if (errorName === JitsiConferenceErrors.CONFERENCE_DESTROYED) {
|
||||
const [ reason ] = action.error.params;
|
||||
const titlekey = Object.keys(TRIGGER_READY_TO_CLOSE_REASONS)[
|
||||
Object.values(TRIGGER_READY_TO_CLOSE_REASONS).indexOf(reason)
|
||||
];
|
||||
|
||||
dispatch(hangup(true, i18next.t(titlekey) || reason));
|
||||
}
|
||||
|
||||
releaseScreenLock();
|
||||
|
||||
break;
|
||||
|
||||
@@ -137,6 +137,7 @@ export interface IConferenceState {
|
||||
followMeEnabled?: boolean;
|
||||
joining?: IJitsiConference;
|
||||
leaving?: IJitsiConference;
|
||||
lobbyWaitingForHost?: boolean;
|
||||
localSubject?: string;
|
||||
locked?: string;
|
||||
membersOnly?: IJitsiConference;
|
||||
@@ -162,6 +163,10 @@ export interface IJitsiConferenceRoom {
|
||||
roomjid: string;
|
||||
}
|
||||
|
||||
interface IConferenceFailedError extends Error {
|
||||
params: Array<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for actions that contain the conference object, so that it can be
|
||||
* stored for use by other action creators.
|
||||
@@ -274,7 +279,7 @@ function _authStatusChanged(state: IConferenceState,
|
||||
* reduction of the specified action.
|
||||
*/
|
||||
function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
conference: IJitsiConference; error: Error; }) {
|
||||
conference: IJitsiConference; error: IConferenceFailedError; }) {
|
||||
// The current (similar to getCurrentConference in
|
||||
// base/conference/functions.any.js) conference which is joining or joined:
|
||||
const conference_ = state.conference || state.joining;
|
||||
@@ -286,6 +291,7 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
let authRequired;
|
||||
let membersOnly;
|
||||
let passwordRequired;
|
||||
let lobbyWaitingForHost;
|
||||
|
||||
switch (error.name) {
|
||||
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED:
|
||||
@@ -293,9 +299,16 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.CONFERENCE_ACCESS_DENIED:
|
||||
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR:
|
||||
case JitsiConferenceErrors.MEMBERS_ONLY_ERROR: {
|
||||
membersOnly = conference;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [ _lobbyJid, _lobbyWaitingForHost ] = error.params;
|
||||
|
||||
lobbyWaitingForHost = _lobbyWaitingForHost;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.PASSWORD_REQUIRED:
|
||||
passwordRequired = conference;
|
||||
@@ -309,6 +322,7 @@ function _conferenceFailed(state: IConferenceState, { conference, error }: {
|
||||
error,
|
||||
joining: undefined,
|
||||
leaving: undefined,
|
||||
lobbyWaitingForHost,
|
||||
|
||||
/**
|
||||
* The indicator of how the conference/room is locked. If falsy, the
|
||||
@@ -365,6 +379,8 @@ function _conferenceJoined(state: IConferenceState, { conference }: { conference
|
||||
membersOnly: undefined,
|
||||
leaving: undefined,
|
||||
|
||||
lobbyWaitingForHost: undefined,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the conference is locked.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type ToolbarButtons = 'camera' |
|
||||
export type ToolbarButton = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
'desktop' |
|
||||
@@ -15,6 +15,9 @@ type ToolbarButtons = 'camera' |
|
||||
'linktosalesforce' |
|
||||
'livestreaming' |
|
||||
'microphone' |
|
||||
'mute-everyone' |
|
||||
'mute-video-everyone' |
|
||||
'noisesuppression' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
@@ -30,6 +33,7 @@ type ToolbarButtons = 'camera' |
|
||||
'tileview' |
|
||||
'toggle-camera' |
|
||||
'videoquality' |
|
||||
'whiteboard' |
|
||||
'__end';
|
||||
|
||||
type ButtonsWithNotifyClick = 'camera' |
|
||||
@@ -270,7 +274,7 @@ export interface IConfig {
|
||||
};
|
||||
corsAvatarURLs?: Array<string>;
|
||||
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
deeplinking?: IDeeplinkingConfig;
|
||||
defaultLanguage?: string;
|
||||
defaultLocalDisplayName?: string;
|
||||
@@ -397,6 +401,7 @@ export interface IConfig {
|
||||
disableResizable?: boolean;
|
||||
disableStageFilmstrip?: boolean;
|
||||
disableTopPanel?: boolean;
|
||||
disabled?: boolean;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
};
|
||||
firefox_fake_device?: string;
|
||||
@@ -573,12 +578,13 @@ export interface IConfig {
|
||||
testMode?: boolean;
|
||||
};
|
||||
tileView?: {
|
||||
disabled?: boolean;
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
tokenAuthUrl?: string;
|
||||
tokenAuthUrlAutoRedirect?: string;
|
||||
tokenLogoutUrl?: string;
|
||||
toolbarButtons?: Array<ToolbarButtons>;
|
||||
toolbarButtons?: Array<ToolbarButton>;
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ToolbarButton } from './configType';
|
||||
|
||||
/**
|
||||
* The prefix of the {@code localStorage} key into which {@link storeConfig}
|
||||
* stores and from which {@link restoreConfig} restores.
|
||||
@@ -13,7 +15,7 @@ export const _CONFIG_STORE_PREFIX = 'config.js';
|
||||
* @protected
|
||||
* @type Array<string>
|
||||
*/
|
||||
export const TOOLBAR_BUTTONS = [
|
||||
export const TOOLBAR_BUTTONS: ToolbarButton[] = [
|
||||
'camera',
|
||||
'chat',
|
||||
'closedcaptions',
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
IDeeplinkingConfig,
|
||||
IDeeplinkingMobileConfig,
|
||||
IDeeplinkingPlatformConfig,
|
||||
NotifyClickButton
|
||||
NotifyClickButton,
|
||||
ToolbarButton
|
||||
} from './configType';
|
||||
import { TOOLBAR_BUTTONS } from './constants';
|
||||
|
||||
@@ -46,7 +47,7 @@ export function getToolbarButtons(state: IReduxState): Array<string> {
|
||||
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
buttons.push(...customButtons as ToolbarButton[]);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
|
||||
@@ -16,8 +16,10 @@ import {
|
||||
IDeeplinkingConfig,
|
||||
IDeeplinkingMobileConfig,
|
||||
IDeeplinkingPlatformConfig,
|
||||
IMobileDynamicLink
|
||||
IMobileDynamicLink,
|
||||
ToolbarButton
|
||||
} from './configType';
|
||||
import { TOOLBAR_BUTTONS } from './constants';
|
||||
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
|
||||
|
||||
/**
|
||||
@@ -546,6 +548,11 @@ function _translateLegacyConfig(oldValue: IConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.disableProfile) {
|
||||
newValue.toolbarButtons = (newValue.toolbarButtons || TOOLBAR_BUTTONS)
|
||||
.filter((button: ToolbarButton) => button !== 'profile');
|
||||
}
|
||||
|
||||
_setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
|
||||
|
||||
return newValue;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { conferenceLeft, conferenceWillLeave, redirect } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
@@ -230,6 +230,9 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_REDIRECTED,
|
||||
_onConnectionRedirected);
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
@@ -298,9 +301,28 @@ export function _connectInternal(id?: string, password?: string) {
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {string|undefined} vnode - The vnode to connect to.
|
||||
* @param {string} focusJid - The focus jid to use.
|
||||
* @param {string|undefined} username - The username to use when joining. This is after promotion from
|
||||
* visitor to main participant.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionRedirected(vnode: string, focusJid: string, username: string) {
|
||||
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_REDIRECTED, _onConnectionRedirected);
|
||||
dispatch(redirect(vnode, focusJid, username));
|
||||
}
|
||||
|
||||
// in case of configured http url for conference request we need the room name
|
||||
const name = getBackendSafeRoomName(state['features/base/conference'].room);
|
||||
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
password,
|
||||
name
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -58,9 +58,10 @@ export function connect(id?: string, password?: string) {
|
||||
*
|
||||
* @param {boolean} [requestFeedback] - Whether to attempt showing a
|
||||
* request for call feedback.
|
||||
* @param {string} [feedbackTitle] - The feedback title.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hangup(requestFeedback = false) {
|
||||
export function hangup(requestFeedback = false, feedbackTitle?: string) {
|
||||
// XXX For web based version we use conference hanging up logic from the old app.
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
if (LocalRecordingManager.isRecordingLocally()) {
|
||||
@@ -76,6 +77,6 @@ export function hangup(requestFeedback = false) {
|
||||
});
|
||||
}
|
||||
|
||||
return APP.conference.hangup(requestFeedback);
|
||||
return APP.conference.hangup(requestFeedback, feedbackTitle);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
import { getLocalTrack } from '../tracks/functions';
|
||||
|
||||
import {
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
@@ -182,13 +184,20 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
case SET_VIDEO_INPUT_DEVICE:
|
||||
case SET_VIDEO_INPUT_DEVICE: {
|
||||
const localTrack = getLocalTrack(store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||
|
||||
// on mobile devices the video stream has to be stopped before replacing it
|
||||
if (isMobileBrowser() && localTrack && !localTrack.muted) {
|
||||
localTrack.jitsiTrack.stopStream();
|
||||
}
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(replaceVideoTrackById(action.deviceId));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPDATE_DEVICE_LIST:
|
||||
logDeviceList(groupDevicesByKind(action.devices));
|
||||
if (areDeviceLabelsInitialized(store.getState())) {
|
||||
|
||||
@@ -18,3 +18,4 @@ export function isMobileBrowser() {
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import COUNTRIES_RESOURCES from 'i18n-iso-countries/langs/en.json';
|
||||
import i18next from 'i18next';
|
||||
import I18nextXHRBackend from 'i18next-xhr-backend';
|
||||
import I18nextXHRBackend, { HttpBackendOptions } from 'i18next-http-backend';
|
||||
import _ from 'lodash';
|
||||
|
||||
import LANGUAGES_RESOURCES from '../../../../lang/languages.json';
|
||||
@@ -61,11 +61,13 @@ export const DEFAULT_LANGUAGE = 'en';
|
||||
/**
|
||||
* The options to initialize i18next with.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {i18next.InitOptions}
|
||||
*/
|
||||
const options = {
|
||||
backend: {
|
||||
loadPath: 'lang/{{ns}}-{{lng}}.json'
|
||||
const options: i18next.InitOptions = {
|
||||
backend: <HttpBackendOptions>{
|
||||
loadPath: (lng: string[], ns: string[]) =>
|
||||
// eslint-disable-next-line no-extra-parens
|
||||
(ns[0] === 'main' ? 'lang/{{ns}}-{{lng}}.json' : 'lang/{{ns}}.json')
|
||||
},
|
||||
defaultNS: 'main',
|
||||
fallbackLng: DEFAULT_LANGUAGE,
|
||||
@@ -76,6 +78,7 @@ const options = {
|
||||
ns: [ 'main', 'languages', 'countries', 'translation-languages' ],
|
||||
react: {
|
||||
// re-render when a new resource bundle is added
|
||||
// @ts-expect-error. Fixed in i18next 19.6.1.
|
||||
bindI18nStore: 'added',
|
||||
useSuspense: false
|
||||
},
|
||||
@@ -89,7 +92,7 @@ const options = {
|
||||
|
||||
i18next
|
||||
.use(navigator.product === 'ReactNative' ? {} : I18nextXHRBackend)
|
||||
.use(languageDetector) // @ts-ignore
|
||||
.use(languageDetector)
|
||||
.init(options);
|
||||
|
||||
// Add default language which is preloaded from the source code.
|
||||
|
||||
@@ -63,6 +63,13 @@ function setupJitsiLocalStorage() {
|
||||
try {
|
||||
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
|
||||
|
||||
// We need to disable the local storage before setting the data in case the browser local storage doesn't
|
||||
// throw exception (in some cases when this happens the local storage may be cleared for every session.
|
||||
// Example: when loading meet from cross-domain with the IFrame API with Brave with the default
|
||||
// configuration). Otherwise we will set the data in the browser local storage and then switch to the dummy
|
||||
// local storage from jitsiLocalStorage and we will loose the data.
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
|
||||
if (typeof localStorageContent === 'object') {
|
||||
Object.keys(localStorageContent).forEach(key => {
|
||||
jitsiLocalStorage.setItem(key, localStorageContent[key]);
|
||||
@@ -72,7 +79,6 @@ function setupJitsiLocalStorage() {
|
||||
logger.error('Can\'t parse localStorageContent.', error);
|
||||
}
|
||||
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
jitsiLocalStorage.on('changed', onFakeLocalStorageChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +120,7 @@ export default class JitsiMeetLogStorage {
|
||||
conference.sendApplicationLog(logMessage);
|
||||
} catch (error) {
|
||||
// NOTE console is intentional here
|
||||
console.error(
|
||||
`Failed to store the logs, msg length: ${logMessage.length}`
|
||||
+ `error: ${JSON.stringify(error)}`);
|
||||
console.error(`Failed to store the logs, msg length: ${logMessage.length} error:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class AudioTrack extends Component<IProps> {
|
||||
/**
|
||||
* Reference to the HTML audio element, stored until the file is ready.
|
||||
*/
|
||||
_ref: HTMLAudioElement | null;
|
||||
_ref: React.RefObject<HTMLAudioElement>;
|
||||
|
||||
/**
|
||||
* The current timeout ID for play() retries.
|
||||
@@ -80,7 +80,7 @@ class AudioTrack extends Component<IProps> {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._errorHandler = this._errorHandler.bind(this);
|
||||
this._setRef = this._setRef.bind(this);
|
||||
this._ref = React.createRef();
|
||||
this._play = this._play.bind(this);
|
||||
}
|
||||
|
||||
@@ -94,19 +94,22 @@ class AudioTrack extends Component<IProps> {
|
||||
componentDidMount() {
|
||||
this._attachTrack(this.props.audioTrack);
|
||||
|
||||
if (this._ref) {
|
||||
if (this._ref?.current) {
|
||||
const audio = this._ref?.current;
|
||||
const { _muted, _volume } = this.props;
|
||||
|
||||
if (typeof _volume === 'number') {
|
||||
this._ref.volume = _volume;
|
||||
audio.volume = _volume;
|
||||
}
|
||||
|
||||
if (typeof _muted === 'boolean') {
|
||||
this._ref.muted = _muted;
|
||||
audio.muted = _muted;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this._ref.addEventListener('error', this._errorHandler);
|
||||
audio.addEventListener('error', this._errorHandler);
|
||||
} else { // This should never happen
|
||||
logger.error(`The react reference is null for AudioTrack ${this.props?.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +124,7 @@ class AudioTrack extends Component<IProps> {
|
||||
this._detachTrack(this.props.audioTrack);
|
||||
|
||||
// @ts-ignore
|
||||
this._ref?.removeEventListener('error', this._errorHandler);
|
||||
this._ref?.current?.removeEventListener('error', this._errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,19 +144,25 @@ class AudioTrack extends Component<IProps> {
|
||||
this._attachTrack(nextProps.audioTrack);
|
||||
}
|
||||
|
||||
if (this._ref) {
|
||||
const currentVolume = this._ref.volume;
|
||||
if (this._ref?.current) {
|
||||
const audio = this._ref?.current;
|
||||
const currentVolume = audio.volume;
|
||||
const nextVolume = nextProps._volume;
|
||||
|
||||
if (typeof nextVolume === 'number' && !isNaN(nextVolume) && currentVolume !== nextVolume) {
|
||||
this._ref.volume = nextVolume;
|
||||
if (nextVolume === 0) {
|
||||
logger.debug(`Setting audio element ${nextProps?.id} volume to 0`);
|
||||
}
|
||||
audio.volume = nextVolume;
|
||||
}
|
||||
|
||||
const currentMuted = this._ref.muted;
|
||||
const currentMuted = audio.muted;
|
||||
const nextMuted = nextProps._muted;
|
||||
|
||||
if (typeof nextMuted === 'boolean' && currentMuted !== nextMuted) {
|
||||
this._ref.muted = nextMuted;
|
||||
logger.debug(`Setting audio element ${nextProps?.id} muted to true`);
|
||||
|
||||
audio.muted = nextMuted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +182,7 @@ class AudioTrack extends Component<IProps> {
|
||||
<audio
|
||||
autoPlay = { autoPlay }
|
||||
id = { id }
|
||||
ref = { this._setRef } />
|
||||
ref = { this._ref } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,12 +194,29 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(track?: ITrack) {
|
||||
const { id } = this.props;
|
||||
|
||||
if (!track?.jitsiTrack) {
|
||||
logger.warn(`Attach is called on audio element ${id} without tracks passed!`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
track.jitsiTrack.attach(this._ref);
|
||||
this._play();
|
||||
if (!this._ref?.current) {
|
||||
logger.warn(`Attempting to attach track ${track?.jitsiTrack} on AudioTrack ${id} without reference!`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
track.jitsiTrack.attach(this._ref.current)
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Attaching the remote track ${track.jitsiTrack} to video with id ${id} has failed with `,
|
||||
error);
|
||||
})
|
||||
.finally(() => {
|
||||
this._play();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,10 +228,10 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_detachTrack(track?: ITrack) {
|
||||
if (this._ref && track && track.jitsiTrack) {
|
||||
if (this._ref?.current && track && track.jitsiTrack) {
|
||||
clearTimeout(this._playTimeout);
|
||||
this._playTimeout = undefined;
|
||||
track.jitsiTrack.detach(this._ref);
|
||||
track.jitsiTrack.detach(this._ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,18 +255,20 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_play(retries = 0) {
|
||||
if (!this._ref) {
|
||||
const { autoPlay, id } = this.props;
|
||||
|
||||
if (!this._ref?.current) {
|
||||
// nothing to play.
|
||||
logger.warn(`Attempting to call play on AudioTrack ${id} without reference!`);
|
||||
|
||||
return;
|
||||
}
|
||||
const { autoPlay, id } = this.props;
|
||||
|
||||
if (autoPlay) {
|
||||
// Ensure the audio gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case the audio may not autoplay.
|
||||
this._ref.play()
|
||||
this._ref.current.play()
|
||||
.then(() => {
|
||||
if (retries !== 0) {
|
||||
// success after some failures
|
||||
@@ -249,7 +277,7 @@ class AudioTrack extends Component<IProps> {
|
||||
logger.info(`Successfully played audio track! retries: ${retries}`);
|
||||
}
|
||||
}, e => {
|
||||
logger.error(`Failed to play audio track! retry: ${retries} ; Error: ${e}`);
|
||||
logger.error(`Failed to play audio track on audio element ${id}! retry: ${retries} ; Error:`, e);
|
||||
|
||||
if (retries < 3) {
|
||||
this._playTimeout = window.setTimeout(() => this._play(retries + 1), 1000);
|
||||
@@ -264,17 +292,6 @@ class AudioTrack extends Component<IProps> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference to the HTML audio element.
|
||||
*
|
||||
* @param {HTMLAudioElement} audioElement - The HTML audio element instance.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setRef(audioElement: HTMLAudioElement | null) {
|
||||
this._ref = audioElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, ReactEventHandler } from 'react';
|
||||
|
||||
import { ITrack } from '../../../tracks/types';
|
||||
import logger from '../../logger';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Video}.
|
||||
@@ -227,13 +228,13 @@ class Video extends Component<IProps> {
|
||||
this._videoElement.onplaying = this._onVideoPlaying;
|
||||
}
|
||||
|
||||
this._attachTrack(this.props.videoTrack);
|
||||
this._attachTrack(this.props.videoTrack).finally(() => {
|
||||
if (this._videoElement && this.props.autoPlay) {
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
|
||||
if (this._videoElement && this.props.autoPlay) {
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
this._videoElement.play()
|
||||
this._videoElement.play()
|
||||
.catch(error => {
|
||||
// Prevent uncaught "DOMException: The play() request was interrupted by a new load request"
|
||||
// when video playback takes long to start and it starts after the component was unmounted.
|
||||
@@ -241,7 +242,8 @@ class Video extends Component<IProps> {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +273,9 @@ class Video extends Component<IProps> {
|
||||
|
||||
if (currentJitsiTrack !== nextJitsiTrack) {
|
||||
this._detachTrack(this.props.videoTrack);
|
||||
this._attachTrack(nextProps.videoTrack);
|
||||
this._attachTrack(nextProps.videoTrack).catch((_error: Error) => {
|
||||
// Ignore the error. We are already logging it.
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.style !== nextProps.style || this.props.className !== nextProps.className) {
|
||||
@@ -321,11 +325,22 @@ class Video extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(videoTrack?: Partial<ITrack>) {
|
||||
const { id } = this.props;
|
||||
|
||||
if (!videoTrack?.jitsiTrack) {
|
||||
return;
|
||||
logger.warn(`Attach is called on video element ${id} without tracks passed!`);
|
||||
|
||||
// returning Promise.resolve just keep the previous logic.
|
||||
// TODO: Check if it make sense to call play on this element or we can just return promise.reject().
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
videoTrack.jitsiTrack.attach(this._videoElement);
|
||||
return videoTrack.jitsiTrack.attach(this._videoElement)
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Attaching the remote track ${videoTrack.jitsiTrack} to video with id ${id} has failed with `,
|
||||
error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -220,21 +220,22 @@ function _setRoom({ dispatch, getState }: IStore, next: Function, action: AnyAct
|
||||
const state = getState();
|
||||
const { room } = action;
|
||||
const roomIsValid = isRoomValid(room);
|
||||
const audioMuted = roomIsValid ? getStartWithAudioMuted(state) : _AUDIO_INITIAL_MEDIA_STATE.muted;
|
||||
const videoMuted = roomIsValid ? getStartWithVideoMuted(state) : _VIDEO_INITIAL_MEDIA_STATE.muted;
|
||||
|
||||
sendAnalytics(
|
||||
createStartMutedConfigurationEvent('local', audioMuted, Boolean(videoMuted)));
|
||||
logger.log(
|
||||
`Start muted: ${audioMuted ? 'audio, ' : ''}${
|
||||
videoMuted ? 'video' : ''}`);
|
||||
// when going to welcomepage on web(room is not valid) we want to skip resetting the values of startWithA/V
|
||||
if (roomIsValid || navigator.product === 'ReactNative') {
|
||||
const audioMuted = roomIsValid ? getStartWithAudioMuted(state) : _AUDIO_INITIAL_MEDIA_STATE.muted;
|
||||
const videoMuted = roomIsValid ? getStartWithVideoMuted(state) : _VIDEO_INITIAL_MEDIA_STATE.muted;
|
||||
|
||||
// Unconditionally express the desires/expectations/intents of the app and
|
||||
// the user i.e. the state of base/media. Eventually, practice/reality i.e.
|
||||
// the state of base/tracks will or will not agree with the desires.
|
||||
dispatch(setAudioMuted(audioMuted));
|
||||
dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
|
||||
dispatch(setVideoMuted(videoMuted));
|
||||
sendAnalytics(createStartMutedConfigurationEvent('local', audioMuted, Boolean(videoMuted)));
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${videoMuted ? 'video' : ''}`);
|
||||
|
||||
// Unconditionally express the desires/expectations/intents of the app and
|
||||
// the user i.e. the state of base/media. Eventually, practice/reality i.e.
|
||||
// the state of base/tracks will or will not agree with the desires.
|
||||
dispatch(setAudioMuted(audioMuted));
|
||||
dispatch(setCameraFacingMode(CAMERA_FACING_MODE.USER));
|
||||
dispatch(setVideoMuted(videoMuted));
|
||||
}
|
||||
|
||||
// startAudioOnly
|
||||
//
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
afterClick?: Function;
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
@@ -108,6 +113,13 @@ export default class AbstractButton<P extends IProps, S=any> extends Component<P
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* A succinct description of what the button does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
|
||||
@@ -9,6 +9,11 @@ import type { IProps as AbstractToolboxItemProps } from './AbstractToolboxItem';
|
||||
|
||||
interface IProps extends AbstractToolboxItemProps {
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the item is displayed in a context menu.
|
||||
*/
|
||||
@@ -60,6 +65,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
*/
|
||||
_renderItem() {
|
||||
const {
|
||||
backgroundColor,
|
||||
contextMenu,
|
||||
disabled,
|
||||
elementAfter,
|
||||
@@ -90,6 +96,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { this.accessibilityLabel }
|
||||
backgroundColor = { backgroundColor }
|
||||
disabled = { disabled }
|
||||
icon = { icon }
|
||||
onClick = { onClick }
|
||||
@@ -128,14 +135,18 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderIcon() {
|
||||
const { customClass, disabled, icon, showLabel, toggled } = this.props;
|
||||
const { backgroundColor, customClass, disabled, icon, showLabel, toggled } = this.props;
|
||||
const iconComponent = (<Icon
|
||||
size = { showLabel ? undefined : 24 }
|
||||
src = { icon } />);
|
||||
const elementType = showLabel ? 'span' : 'div';
|
||||
const className = `${showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon'} ${
|
||||
toggled ? 'toggled' : ''} ${disabled ? 'disabled' : ''} ${customClass ?? ''}`;
|
||||
const style = backgroundColor && !showLabel ? { backgroundColor } : {};
|
||||
|
||||
return React.createElement(elementType, { className }, iconComponent);
|
||||
return React.createElement(elementType, {
|
||||
className,
|
||||
style
|
||||
}, iconComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,23 @@ import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions
|
||||
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
|
||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { openDialog } from '../dialog/actions';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { setScreenshareMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
|
||||
import {
|
||||
addLocalTrack,
|
||||
replaceLocalTrack
|
||||
replaceLocalTrack,
|
||||
toggleCamera
|
||||
} from './actions.any';
|
||||
import AllowToggleCameraDialog from './components/web/AllowToggleCameraDialog';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getLocalDesktopTrack,
|
||||
getLocalJitsiAudioTrack
|
||||
getLocalJitsiAudioTrack,
|
||||
getLocalVideoTrack,
|
||||
isToggleCameraEnabled
|
||||
} from './functions';
|
||||
import { IShareOptions, IToggleScreenSharingOptions } from './types';
|
||||
|
||||
@@ -263,3 +268,52 @@ async function _toggleScreenSharing(
|
||||
APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the camera facing mode(environment/user). If facing mode not provided, it will do a toggle.
|
||||
*
|
||||
* @param {string | undefined} facingMode - The selected facing mode.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setCameraFacingMode(facingMode: string | undefined) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
if (!isToggleCameraEnabled(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!facingMode) {
|
||||
dispatch(toggleCamera());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
if (!tracks || !localVideoTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||
|
||||
if (currentFacingMode !== facingMode) {
|
||||
dispatch(toggleCamera());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to open the permission dialog for toggling camera remotely.
|
||||
*
|
||||
* @param {Function} onAllow - Callback to be executed if permission to toggle camera was granted.
|
||||
* @param {string} initiatorId - The participant id of the requester.
|
||||
* @returns {Object} - The open dialog action.
|
||||
*/
|
||||
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
|
||||
return openDialog(AllowToggleCameraDialog, {
|
||||
onAllow,
|
||||
initiatorId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { translate } from '../../../i18n/functions';
|
||||
import { getParticipantDisplayName } from '../../../participants/functions';
|
||||
import Dialog from '../../../ui/components/web/Dialog';
|
||||
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The participant id of the toggle camera requester.
|
||||
*/
|
||||
initiatorId: string;
|
||||
|
||||
/**
|
||||
* Function to be invoked after permission to toggle camera granted.
|
||||
*/
|
||||
onAllow: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog to allow toggling camera remotely.
|
||||
*
|
||||
* @returns {JSX.Element} - The allow toggle camera dialog.
|
||||
*/
|
||||
const AllowToggleCameraDialog = ({ onAllow, t, initiatorId }: IProps): JSX.Element => {
|
||||
const initiatorName = useSelector((state: IReduxState) => getParticipantDisplayName(state, initiatorId));
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{ translationKey: 'dialog.allow' }}
|
||||
onSubmit = { onAllow }
|
||||
titleKey = 'dialog.allowToggleCameraTitle'>
|
||||
<div>
|
||||
{ t('dialog.allowToggleCameraDialog', { initiatorName }) }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(AllowToggleCameraDialog);
|
||||
4
react/features/base/tracks/constants.ts
Normal file
4
react/features/base/tracks/constants.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The payload name for remotely setting the camera facing mode message.
|
||||
*/
|
||||
export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message';
|
||||
@@ -2,7 +2,7 @@
|
||||
* Loads the enabled stream effects.
|
||||
*
|
||||
* @param {Object} _store - The Redux store.
|
||||
* @returns {Promsie} - A Promise which resolves with an array of the loaded effects.
|
||||
* @returns {Promise} - A Promise which resolves with an array of the loaded effects.
|
||||
*/
|
||||
export default function loadEffects(_store: Object): Promise<Array<any>> {
|
||||
return Promise.resolve([]);
|
||||
|
||||
@@ -8,7 +8,7 @@ import logger from './logger';
|
||||
* Loads the enabled stream effects.
|
||||
*
|
||||
* @param {Object} store - The Redux store.
|
||||
* @returns {Promsie} - A Promise which resolves when all effects are created.
|
||||
* @returns {Promise} - A Promise which resolves when all effects are created.
|
||||
*/
|
||||
export default function loadEffects(store: IStore): Promise<any> {
|
||||
const state = store.getState();
|
||||
|
||||
@@ -80,31 +80,31 @@ const Input = forwardRef<TextInput, IProps>(({
|
||||
const { nativeEvent: { text } } = e;
|
||||
|
||||
onChange?.(text);
|
||||
}, []);
|
||||
}, [ onChange ]);
|
||||
|
||||
const clearInput = useCallback(() => {
|
||||
onChange?.('');
|
||||
}, []);
|
||||
}, [ onChange ]);
|
||||
|
||||
const handleBlur = useCallback((e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
||||
setFocused(false);
|
||||
onBlur?.(e);
|
||||
}, []);
|
||||
}, [ onBlur ]);
|
||||
|
||||
const handleFocus = useCallback((e: NativeSyntheticEvent<TextInputFocusEventData>) => {
|
||||
setFocused(true);
|
||||
onFocus?.(e);
|
||||
}, []);
|
||||
}, [ onFocus ]);
|
||||
|
||||
const handleKeyPress = useCallback((e: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
|
||||
onKeyPress?.(e);
|
||||
}, []);
|
||||
}, [ onKeyPress ]);
|
||||
|
||||
const handleSubmitEditing = useCallback((e: NativeSyntheticEvent<TextInputSubmitEditingEventData>) => {
|
||||
const { nativeEvent: { text } } = e;
|
||||
|
||||
onSubmitEditing?.(text);
|
||||
}, []);
|
||||
}, [ onSubmitEditing ]);
|
||||
|
||||
return (<View style = { [ styles.inputContainer, customStyles?.container ] }>
|
||||
{label && <Text style = { styles.label }>{ label }</Text>}
|
||||
|
||||
@@ -139,6 +139,7 @@ export interface IProps {
|
||||
onClose?: () => void;
|
||||
size?: 'large' | 'medium';
|
||||
submit?: () => void;
|
||||
testId?: string;
|
||||
title?: string;
|
||||
titleKey?: string;
|
||||
}
|
||||
@@ -152,6 +153,7 @@ const BaseDialog = ({
|
||||
onClose,
|
||||
size = 'medium',
|
||||
submit,
|
||||
testId,
|
||||
title,
|
||||
titleKey
|
||||
}: IProps) => {
|
||||
@@ -179,7 +181,9 @@ const BaseDialog = ({
|
||||
}, [ handleKeyDown ]);
|
||||
|
||||
return (
|
||||
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
|
||||
<div
|
||||
className = { cx(classes.container, isUnmounting && 'unmount') }
|
||||
data-testid = { testId }>
|
||||
<div className = { classes.backdrop } />
|
||||
<FocusOn
|
||||
className = { classes.focusLock }
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface IProps {
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* The context menu item background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Component children.
|
||||
*/
|
||||
@@ -163,6 +168,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
const ContextMenuItem = ({
|
||||
accessibilityLabel,
|
||||
backgroundColor,
|
||||
children,
|
||||
className,
|
||||
controls,
|
||||
@@ -181,7 +187,7 @@ const ContextMenuItem = ({
|
||||
textClassName }: IProps) => {
|
||||
const { classes: styles, cx } = useStyles();
|
||||
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
|
||||
|
||||
const style = backgroundColor ? { backgroundColor } : {};
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
// only trigger the fallback behavior (onClick) if we dont have any explicit keyboard event handler
|
||||
if (onClick && !onKeyPress && !onKeyDown && (e.key === 'Enter' || e.key === ' ')) {
|
||||
@@ -223,6 +229,7 @@ const ContextMenuItem = ({
|
||||
onKeyDown = { disabled ? undefined : onKeyDown }
|
||||
onKeyPress = { disabled ? undefined : onKeyPressHandler }
|
||||
role = { onClick ? role : undefined }
|
||||
style = { style }
|
||||
tabIndex = { onClick ? tabIndex : undefined }>
|
||||
{customIcon ? customIcon
|
||||
: icon && <Icon
|
||||
|
||||
@@ -96,6 +96,7 @@ const Dialog = ({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
size,
|
||||
testId,
|
||||
title,
|
||||
titleKey
|
||||
}: IDialogProps) => {
|
||||
@@ -127,6 +128,7 @@ const Dialog = ({
|
||||
onClose = { onClose }
|
||||
size = { size }
|
||||
submit = { submit }
|
||||
testId = { testId }
|
||||
title = { title }
|
||||
titleKey = { titleKey }>
|
||||
<div className = { classes.header }>
|
||||
|
||||
@@ -260,6 +260,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
padding: 6,
|
||||
textAlign: 'center' as const,
|
||||
pointerEvents: 'all' as const,
|
||||
display: 'flex',
|
||||
boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)',
|
||||
|
||||
'& > div': {
|
||||
|
||||
@@ -5,6 +5,8 @@ import zxcvbn from 'zxcvbn';
|
||||
// The null UUID.
|
||||
const NIL_UUID = parseUUID(NIL);
|
||||
|
||||
const _zxcvbnCache = new Map();
|
||||
|
||||
/**
|
||||
* Checks if the given string is a valid UUID or not.
|
||||
*
|
||||
@@ -23,6 +25,24 @@ function isValidUUID(str: string) {
|
||||
return !_.isEqual(uuid, NIL_UUID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a room name and caches the result.
|
||||
*
|
||||
* @param {string} roomName - The room name.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _checkRoomName(roomName = '') {
|
||||
if (_zxcvbnCache.has(roomName)) {
|
||||
return _zxcvbnCache.get(roomName);
|
||||
}
|
||||
|
||||
const result = zxcvbn(roomName);
|
||||
|
||||
_zxcvbnCache.set(roomName, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the room name is considered a weak (insecure) one.
|
||||
*
|
||||
@@ -30,5 +50,7 @@ function isValidUUID(str: string) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export default function isInsecureRoomName(roomName = ''): boolean {
|
||||
return !isValidUUID(roomName) && zxcvbn(roomName).score < 3;
|
||||
|
||||
// room names longer than 200 chars we consider secure
|
||||
return !isValidUUID(roomName) && (roomName.length < 200 && _checkRoomName(roomName).score < 3);
|
||||
}
|
||||
|
||||
@@ -631,3 +631,29 @@ export function appendURLParam(url: string, name: string, value: string) {
|
||||
|
||||
return newUrl.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new hash param to a url string.
|
||||
* Checks whether to use '?' or '&' as a separator (checks for already existing params).
|
||||
*
|
||||
* @param {string} url - The url to modify.
|
||||
* @param {string} name - The param name to add.
|
||||
* @param {string} value - The value for the param.
|
||||
*
|
||||
* @returns {string} - The modified url.
|
||||
*/
|
||||
export function appendURLHashParam(url: string, name: string, value: string) {
|
||||
const newUrl = new URL(url);
|
||||
const dummyUrl = new URL('https://example.com');
|
||||
|
||||
// Copy current hash-parameters without the '#' as search-parameters.
|
||||
dummyUrl.search = newUrl.hash.substring(1);
|
||||
|
||||
// Set or update value with the searchParams-API.
|
||||
dummyUrl.searchParams.append(name, value);
|
||||
|
||||
// Write back as hash parameters.
|
||||
newUrl.hash = dummyUrl.searchParams.toString();
|
||||
|
||||
return newUrl.toString();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getParticipantDisplayName, isLocalParticipantModerator } from '../../base/participants/functions';
|
||||
import { setLobbyChatActiveState } from '../actions.any';
|
||||
import { setPrivateMessageRecipient } from '../actions.web';
|
||||
import { setLobbyChatActiveState, setPrivateMessageRecipient } from '../actions.any';
|
||||
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
|
||||
@@ -130,7 +130,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// recipient. This logic tries to mitigate this risk.
|
||||
const shouldSendPrivateMessageTo = _shouldSendPrivateMessageTo(state, action);
|
||||
|
||||
if (shouldSendPrivateMessageTo) {
|
||||
const participantExists = shouldSendPrivateMessageTo
|
||||
&& getParticipantById(state, shouldSendPrivateMessageTo);
|
||||
|
||||
if (shouldSendPrivateMessageTo && participantExists) {
|
||||
dispatch(openDialog(ChatPrivacyDialog, {
|
||||
message: action.message,
|
||||
participantID: shouldSendPrivateMessageTo
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
import AlertDialog from '../base/dialog/components/native/AlertDialog';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
|
||||
@@ -34,6 +34,38 @@ export function notifyKickedOut(participant: any, submit?: Function) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
*
|
||||
* @param {string} reasonKey - The translation key for the reason why the conference failed.
|
||||
* @param {?Function} submit - The function to execute after submiting the dialog.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function notifyConferenceFailed(reasonKey: string, submit?: Function) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
if (!reasonKey) {
|
||||
submit?.();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// we have to push the opening of the dialog to the queue
|
||||
// so that we make sure it will be visible after the events
|
||||
// of conference destroyed are done
|
||||
setTimeout(() => dispatch(openDialog(AlertDialog, {
|
||||
contentKey: {
|
||||
key: reasonKey
|
||||
},
|
||||
params: {
|
||||
},
|
||||
onSubmit: () => {
|
||||
submit?.();
|
||||
dispatch(hideDialog(AlertDialog));
|
||||
}
|
||||
})));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses calendar notification about next or ongoing event.
|
||||
*
|
||||
|
||||
@@ -1,41 +1,26 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { configureInitialDevices } from '../base/devices/actions.web';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { getBackendSafeRoomName } from '../base/util/uri';
|
||||
import { showNotification } from '../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, NOTIFICATION_TYPE } from '../notifications/constants';
|
||||
|
||||
import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
|
||||
import LeaveReasonDialog from './components/web/LeaveReasonDialog.web';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
* Opens {@code LeaveReasonDialog}.
|
||||
*
|
||||
* @param {JitsiParticipant} participant - The {@link JitsiParticipant}
|
||||
* instance which initiated the kick event.
|
||||
* @param {?Function} _ - Used only in native code.
|
||||
* @returns {Function}
|
||||
* @param {string} [title] - The dialog title.
|
||||
*
|
||||
* @returns {Promise} Resolved when the dialog is closed.
|
||||
*/
|
||||
export function notifyKickedOut(participant: any, _?: Function) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
if (!participant || participant?.isReplaced()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = {
|
||||
participantDisplayName:
|
||||
getParticipantDisplayName(getState, participant.getId())
|
||||
};
|
||||
|
||||
dispatch(showNotification({
|
||||
appearance: NOTIFICATION_TYPE.ERROR,
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.kickMessage',
|
||||
descriptionArguments: args,
|
||||
titleKey: 'dialog.kickTitle',
|
||||
titleArguments: args
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
};
|
||||
export function openLeaveReasonDialog(title?: string) {
|
||||
return (dispatch: IStore['dispatch']): Promise<void> => new Promise(resolve => {
|
||||
dispatch(openDialog(LeaveReasonDialog, {
|
||||
onClose: resolve,
|
||||
title
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ import Chat from '../../../chat/components/web/Chat';
|
||||
import MainFilmstrip from '../../../filmstrip/components/web/MainFilmstrip';
|
||||
import ScreenshareFilmstrip from '../../../filmstrip/components/web/ScreenshareFilmstrip';
|
||||
import StageFilmstrip from '../../../filmstrip/components/web/StageFilmstrip';
|
||||
import { isFilmstripDisabled } from '../../../filmstrip/functions.web';
|
||||
import CalleeInfoContainer from '../../../invite/components/callee-info/CalleeInfoContainer';
|
||||
import LargeVideo from '../../../large-video/components/LargeVideo.web';
|
||||
import LobbyScreen from '../../../lobby/components/web/LobbyScreen';
|
||||
@@ -63,6 +64,11 @@ interface IProps extends AbstractProps, WithTranslation {
|
||||
*/
|
||||
_backgroundAlpha?: number;
|
||||
|
||||
/**
|
||||
* Whether the vertical/horizonal filmstrip is disabled.
|
||||
*/
|
||||
_filmstripDisabled: boolean;
|
||||
|
||||
/**
|
||||
* Are any overlays visible?
|
||||
*/
|
||||
@@ -199,12 +205,14 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_filmstripDisabled,
|
||||
_isAnyOverlayVisible,
|
||||
_layoutClassName,
|
||||
_notificationsVisible,
|
||||
_overflowDrawer,
|
||||
_showLobby,
|
||||
_showPrejoin,
|
||||
_shouldDisplayTileView,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
@@ -230,7 +238,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
_showPrejoin || _showLobby || (<>
|
||||
<StageFilmstrip />
|
||||
<ScreenshareFilmstrip />
|
||||
<MainFilmstrip />
|
||||
{ (!_filmstripDisabled || _shouldDisplayTileView) && <MainFilmstrip /> }
|
||||
</>)
|
||||
}
|
||||
</div>
|
||||
@@ -396,6 +404,7 @@ function _mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_backgroundAlpha: backgroundAlpha,
|
||||
_filmstripDisabled: isFilmstripDisabled(state),
|
||||
_isAnyOverlayVisible: Boolean(getOverlayToRender(state)),
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state) ?? ''],
|
||||
_mouseMoveCallbackInterval: mouseMoveCallbackInterval,
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
dialog: {
|
||||
marginBottom: theme.spacing(1)
|
||||
},
|
||||
|
||||
text: {
|
||||
fontSize: '20px'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link LeaveReasonDialog}.
|
||||
*/
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Callback invoked when {@code LeaveReasonDialog} is unmounted.
|
||||
*/
|
||||
onClose: () => void;
|
||||
|
||||
/**
|
||||
* The title to display in the dialog.
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React {@code Component} for displaying a dialog with a reason that ended the conference.
|
||||
*
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
const LeaveReasonDialog = ({ onClose, title }: IProps) => {
|
||||
const { classes } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => () => {
|
||||
onClose?.();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ hidden: true }}
|
||||
onSubmit = { onClose }
|
||||
size = 'medium'
|
||||
testId = 'dialog.leaveReason'>
|
||||
<div className = { classes.dialog }>
|
||||
{title ? <div className = { classes.text }>{t(title)}</div> : null}
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeaveReasonDialog;
|
||||
@@ -9,10 +9,8 @@ import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT,
|
||||
KICKED_OUT
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference/actionTypes';
|
||||
import { conferenceLeft } from '../base/conference/actions';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
@@ -41,7 +39,7 @@ import { showSalesforceNotification } from '../salesforce/actions';
|
||||
import { setToolboxEnabled } from '../toolbox/actions.any';
|
||||
|
||||
import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes';
|
||||
import { dismissCalendarNotification, notifyKickedOut } from './actions';
|
||||
import { dismissCalendarNotification } from './actions';
|
||||
import { IFRAME_DISABLED_TIMEOUT_MINUTES, IFRAME_EMBED_ALLOWED_LOCATIONS } from './constants';
|
||||
|
||||
|
||||
@@ -64,20 +62,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
}
|
||||
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(notifyKickedOut(
|
||||
action.participant,
|
||||
() => {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DISMISS_CALENDAR_NOTIFICATION:
|
||||
case CONFERENCE_LEFT:
|
||||
case CONFERENCE_FAILED: {
|
||||
28
react/features/conference/middleware.native.ts
Normal file
28
react/features/conference/middleware.native.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { appNavigate } from '../app/actions.native';
|
||||
import { KICKED_OUT } from '../base/conference/actionTypes';
|
||||
import { conferenceLeft } from '../base/conference/actions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { notifyKickedOut } from './actions.native';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(notifyKickedOut(
|
||||
action.participant,
|
||||
() => {
|
||||
dispatch(conferenceLeft(action.conference));
|
||||
dispatch(appNavigate(undefined));
|
||||
}
|
||||
));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
61
react/features/conference/middleware.web.ts
Normal file
61
react/features/conference/middleware.web.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
|
||||
import i18next from 'i18next';
|
||||
|
||||
import { CONFERENCE_JOINED, KICKED_OUT } from '../base/conference/actionTypes';
|
||||
import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { hangup } from '../base/connection/actions.web';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web';
|
||||
import { CAMERA_FACING_MODE_MESSAGE } from '../base/tracks/constants';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
_addSetCameraFacingModeListener(action.conference);
|
||||
break;
|
||||
}
|
||||
|
||||
case KICKED_OUT: {
|
||||
const { dispatch } = store;
|
||||
const { participant } = action;
|
||||
|
||||
const participantDisplayName
|
||||
= getParticipantDisplayName(store.getState, participant.getId());
|
||||
|
||||
dispatch(hangup(true, i18next.t('dialog.kickTitle', { participantDisplayName })));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that
|
||||
* will perform various chat related activities.
|
||||
*
|
||||
* @param {IJitsiConference} conference - The conference.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addSetCameraFacingModeListener(conference: IJitsiConference) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||
(...args: any) => {
|
||||
if (args && args.length >= 2) {
|
||||
const [ sender, eventData ] = args;
|
||||
|
||||
if (eventData.name === CAMERA_FACING_MODE_MESSAGE) {
|
||||
APP.store.dispatch(openAllowToggleCameraDialog(
|
||||
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)),
|
||||
/* initiatorId */ sender._id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -79,10 +79,8 @@ export function submitVideoDeviceSelectionTab(newState: any, isDisplayedOnWelcom
|
||||
userSelectedCameraDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
|
||||
}));
|
||||
|
||||
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
|
||||
}
|
||||
|
||||
if (newState.localFlipX !== currentState.localFlipX) {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: newState.localFlipX
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getDeviceIdByLabel,
|
||||
groupDevicesByKind
|
||||
} from '../base/devices/functions.web';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { isIosMobileBrowser, isMobileBrowser } from '../base/environment/utils';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import {
|
||||
@@ -97,7 +97,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
|
||||
export function getVideoDeviceSelectionDialogProps(stateful: IStateful, isDisplayedOnWelcomePage: boolean) {
|
||||
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
|
||||
// by the browser when a new track is created for preview. That's why we are disabling all previews.
|
||||
const disablePreviews = isIosMobileBrowser();
|
||||
const disablePreviews = isMobileBrowser();
|
||||
|
||||
const state = toState(stateful);
|
||||
const settings = state['features/base/settings'];
|
||||
|
||||
@@ -41,14 +41,16 @@ export function cancelFeedback(score: number, message: string) {
|
||||
* @param {JistiConference} conference - The conference for which the feedback
|
||||
* would be about. The conference is passed in because feedback can occur after
|
||||
* a conference has been left, so references to it may no longer exist in redux.
|
||||
* @param {string} title - The feedback dialog title.
|
||||
* @returns {Promise} Resolved with value - false if the dialog is enabled and
|
||||
* resolved with true if the dialog is disabled or the feedback was already
|
||||
* submitted. Rejected if another dialog is already displayed.
|
||||
*/
|
||||
export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
export function maybeOpenFeedbackDialog(conference: IJitsiConference, title?: string) {
|
||||
type R = {
|
||||
feedbackSubmitted: boolean;
|
||||
showThankYou: boolean;
|
||||
wasDialogShown: boolean;
|
||||
};
|
||||
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<R> => {
|
||||
@@ -67,16 +69,18 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
|
||||
return Promise.resolve({
|
||||
feedbackSubmitted: true,
|
||||
showThankYou: true
|
||||
showThankYou: true,
|
||||
wasDialogShown: false
|
||||
});
|
||||
} else if (conference.isCallstatsEnabled() && feedbackPercentage > Math.random() * 100) {
|
||||
return new Promise(resolve => {
|
||||
dispatch(openFeedbackDialog(conference, () => {
|
||||
dispatch(openFeedbackDialog(conference, title, () => {
|
||||
const { submitted } = getState()['features/feedback'];
|
||||
|
||||
resolve({
|
||||
feedbackSubmitted: submitted,
|
||||
showThankYou: false
|
||||
showThankYou: false,
|
||||
wasDialogShown: true
|
||||
});
|
||||
}));
|
||||
});
|
||||
@@ -87,7 +91,8 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
// act on it.
|
||||
return Promise.resolve({
|
||||
feedbackSubmitted: false,
|
||||
showThankYou: true
|
||||
showThankYou: true,
|
||||
wasDialogShown: false
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -98,14 +103,16 @@ export function maybeOpenFeedbackDialog(conference: IJitsiConference) {
|
||||
* @param {JitsiConference} conference - The JitsiConference that is being
|
||||
* rated. The conference is passed in because feedback can occur after a
|
||||
* conference has been left, so references to it may no longer exist in redux.
|
||||
* @param {string} [title] - The feedback dialog title.
|
||||
* @param {Function} [onClose] - An optional callback to invoke when the dialog
|
||||
* is closed.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function openFeedbackDialog(conference?: IJitsiConference, onClose?: Function) {
|
||||
export function openFeedbackDialog(conference?: IJitsiConference, title?: string, onClose?: Function) {
|
||||
return openDialog(FeedbackDialog, {
|
||||
conference,
|
||||
onClose
|
||||
onClose,
|
||||
title
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,10 @@ const useStyles = makeStyles()(theme => {
|
||||
}
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: '16px'
|
||||
},
|
||||
|
||||
details: {
|
||||
'& textarea': {
|
||||
minHeight: '122px'
|
||||
@@ -98,6 +102,11 @@ interface IProps {
|
||||
* Callback invoked when {@code FeedbackDialog} is unmounted.
|
||||
*/
|
||||
onClose: Function;
|
||||
|
||||
/**
|
||||
* The title to display in the dialog. Usually the reason that triggered the feedback.
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +117,7 @@ interface IProps {
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
const FeedbackDialog = ({ conference, onClose }: IProps) => {
|
||||
const FeedbackDialog = ({ conference, onClose, title }: IProps) => {
|
||||
const { classes } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
@@ -284,6 +293,7 @@ const FeedbackDialog = ({ conference, onClose }: IProps) => {
|
||||
size = 'large'
|
||||
titleKey = 'feedback.rateExperience'>
|
||||
<div className = { classes.dialog }>
|
||||
{title ? <div className = { classes.title }>{t(title)}</div> : null}
|
||||
<div className = { classes.rating }>
|
||||
<div
|
||||
className = { classes.stars }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
getActiveSpeakersToBeDisplayed,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
@@ -95,3 +95,15 @@ export function updateRemoteParticipantsOnLeave(store: IStore, participantId: st
|
||||
reorderedParticipants.delete(participantId)
|
||||
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether tileview is completely disabled.
|
||||
*
|
||||
* @param {IReduxState} state - Redux state.
|
||||
* @returns {boolean} - Whether tileview is completely disabled.
|
||||
*/
|
||||
export function isTileViewModeDisabled(state: IReduxState) {
|
||||
const { tileView = {} } = state['features/base/config'];
|
||||
|
||||
return tileView.disabled;
|
||||
}
|
||||
|
||||
@@ -763,6 +763,18 @@ export function isStageFilmstripEnabled(state: IReduxState) {
|
||||
return Boolean(!filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the vertical/horizontal filmstrip is disabled.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isFilmstripDisabled(state: IReduxState) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
|
||||
return Boolean(filmstrip?.disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thumbnail type by filmstrip type.
|
||||
*
|
||||
|
||||
@@ -120,7 +120,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case SETTINGS_UPDATED: {
|
||||
if (typeof action.settings?.localFlipX === 'boolean') {
|
||||
// TODO: This needs to be removed once the large video is Reactified.
|
||||
VideoLayout.onLocalFlipXChanged();
|
||||
VideoLayout.onLocalFlipXChanged(action.settings.localFlipX);
|
||||
}
|
||||
if (action.settings?.disableSelfView) {
|
||||
const state = store.getState();
|
||||
|
||||
@@ -56,7 +56,7 @@ const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numb
|
||||
if (countryCode) {
|
||||
return (
|
||||
<td className = 'flag-cell'>
|
||||
{countryCode === 'SIP'
|
||||
{countryCode === 'SIP' || countryCode === 'SIP_AUDIO_ONLY'
|
||||
? <Icon src = { IconSip } />
|
||||
: <i className = { `flag iti-flag ${countryCode}` } />
|
||||
}
|
||||
@@ -129,6 +129,8 @@ const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numb
|
||||
|
||||
if (countryCode === 'SIP') {
|
||||
countryName = t('info.sip');
|
||||
} else if (countryCode === 'SIP_AUDIO_ONLY') {
|
||||
countryName = t('info.sipAudioOnly');
|
||||
} else {
|
||||
countryName = t(`countries:countries.${countryCode}`);
|
||||
|
||||
|
||||
@@ -445,7 +445,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
const { disableLobbyPassword } = getSecurityUiConfig(state);
|
||||
const showCopyUrlButton = inviteEnabledFlag || !disableInviteFunctions;
|
||||
const deviceStatusVisible = isDeviceStatusVisible(state);
|
||||
const { membersOnly } = state['features/base/conference'];
|
||||
const { membersOnly, lobbyWaitingForHost } = state['features/base/conference'];
|
||||
const { isLobbyChatActive, lobbyMessageRecipient, messages } = state['features/chat'];
|
||||
|
||||
return {
|
||||
@@ -461,7 +461,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
_participantId: participantId,
|
||||
_participantName: localParticipant?.name,
|
||||
_passwordJoinFailed: passwordJoinFailed,
|
||||
_renderPassword: !iAmSipGateway && !disableLobbyPassword,
|
||||
_renderPassword: !iAmSipGateway && !disableLobbyPassword && !lobbyWaitingForHost,
|
||||
showCopyUrlButton
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
customStyles = {{ input: styles.customInput }}
|
||||
error = { _passwordJoinFailed }
|
||||
onChange = { this._onChangePassword }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
placeholder = { t('lobby.enterPasswordButton') }
|
||||
secureTextEntry = { true }
|
||||
value = { this.state.password } />
|
||||
);
|
||||
|
||||
@@ -189,7 +189,7 @@ class LobbyScreen extends AbstractLobbyScreen<IProps> {
|
||||
className = { `lobby-prejoin-input ${_passwordJoinFailed ? 'error' : ''}` }
|
||||
id = 'lobby-password-input'
|
||||
onChange = { this._onChangePassword }
|
||||
placeholder = { t('lobby.passwordField') }
|
||||
placeholder = { t('lobby.enterPasswordButton') }
|
||||
testId = 'lobby.password'
|
||||
type = 'password'
|
||||
value = { this.state.password } />
|
||||
|
||||
22
react/features/mobile/full-screen/functions.ts
Normal file
22
react/features/mobile/full-screen/functions.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { isLocalVideoTrackDesktop } from '../../base/tracks/functions.any';
|
||||
|
||||
/**
|
||||
* Checks whether full-screen state should be used or not.
|
||||
*
|
||||
* @param {IReduxState} state - The redux state.
|
||||
* @returns {boolean} - Whether full-screen state shuld be used or not.
|
||||
*/
|
||||
export function shouldUseFullScreen(state: IReduxState) {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
const isDesktopSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
return conference ? !audioOnly && !dialogOpen && !isDesktopSharing && fullscreenEnabled : false;
|
||||
}
|
||||
@@ -2,14 +2,11 @@ import ImmersiveMode from 'react-native-immersive-mode';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import { getCurrentConference } from '../../base/conference/functions';
|
||||
import { isAnyDialogOpen } from '../../base/dialog/functions';
|
||||
import { FULLSCREEN_ENABLED } from '../../base/flags/constants';
|
||||
import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
import StateListenerRegistry from '../../base/redux/StateListenerRegistry';
|
||||
|
||||
import { _setImmersiveSubscription } from './actions';
|
||||
import { shouldUseFullScreen } from './functions';
|
||||
import logger from './logger';
|
||||
|
||||
type BarVisibilityType = {
|
||||
@@ -48,14 +45,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
});
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
|
||||
return conference ? !audioOnly && !dialogOpen && fullscreenEnabled : false;
|
||||
},
|
||||
/* selector */ shouldUseFullScreen,
|
||||
/* listener */ fullScreen => _setFullScreen(fullScreen)
|
||||
);
|
||||
|
||||
@@ -73,13 +63,7 @@ function _onImmersiveChange({ getState }: IStore) {
|
||||
const { appState } = state['features/background'];
|
||||
|
||||
if (appState === 'active') {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const conference = getCurrentConference(state);
|
||||
const dialogOpen = isAnyDialogOpen(state);
|
||||
const fullscreenEnabled = getFeatureFlag(state, FULLSCREEN_ENABLED, true);
|
||||
const fullScreen = conference ? !audioOnly && !dialogOpen && fullscreenEnabled : false;
|
||||
|
||||
_setFullScreen(fullScreen);
|
||||
_setFullScreen(shouldUseFullScreen(state));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ import { navigationStyles } from './styles';
|
||||
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* Icon button color.
|
||||
*/
|
||||
color?: string;
|
||||
|
||||
/**
|
||||
* Is the button disabled?
|
||||
*/
|
||||
@@ -42,7 +47,7 @@ interface IProps {
|
||||
twoActions?: boolean;
|
||||
}
|
||||
|
||||
const HeaderNavigationButton = ({ disabled, label, onPress, src, style, twoActions }: IProps) => {
|
||||
const HeaderNavigationButton = ({ color, disabled, label, onPress, src, style, twoActions }: IProps) => {
|
||||
|
||||
let btnStyle;
|
||||
let labelStyle;
|
||||
@@ -64,6 +69,7 @@ const HeaderNavigationButton = ({ disabled, label, onPress, src, style, twoActio
|
||||
{
|
||||
src ? (
|
||||
<IconButton
|
||||
color = { color }
|
||||
onPress = { onPress }
|
||||
size = { 24 }
|
||||
src = { src }
|
||||
|
||||
@@ -10,7 +10,7 @@ export const settingsNavigationContainerRef = React.createRef<NavigationContaine
|
||||
* @param {Object} params - Params to pass to the destination route.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function navigate(name: string, params: Object) {
|
||||
export function navigate(name: string, params?: Object) {
|
||||
return settingsNavigationContainerRef.current?.navigate(name, params);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@ import { createStackNavigator } from '@react-navigation/stack';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import LanguageSelectView from '../../../../../settings/components/native/LanguageSelectView';
|
||||
import ProfileView from '../../../../../settings/components/native/ProfileView';
|
||||
import SettingsView
|
||||
from '../../../../../settings/components/native/SettingsView';
|
||||
import { screen } from '../../../routes';
|
||||
import {
|
||||
languageSelectScreenOptions,
|
||||
navigationContainerTheme,
|
||||
profileSettingsScreenOptions,
|
||||
settingsScreenOptions,
|
||||
welcomeScreenOptions
|
||||
} from '../../../screenOptions';
|
||||
@@ -37,10 +41,19 @@ const SettingsNavigationContainer = ({ isInWelcomePage }: IProps) => {
|
||||
const SettingsScreen = useCallback(() =>
|
||||
(
|
||||
<SettingsView
|
||||
addBottomInset = { !isInWelcomePage }
|
||||
scrollBounces = { isInWelcomePage } />
|
||||
isInWelcomePage = { isInWelcomePage } />
|
||||
), []);
|
||||
|
||||
const ProfileScreen = useCallback(() =>
|
||||
(<ProfileView
|
||||
isInWelcomePage = { isInWelcomePage } />)
|
||||
, []);
|
||||
|
||||
const LanguageSelectScreen = useCallback(() =>
|
||||
(<LanguageSelectView
|
||||
isInWelcomePage = { isInWelcomePage } />)
|
||||
, []);
|
||||
|
||||
return (
|
||||
<NavigationContainer
|
||||
independent = { true }
|
||||
@@ -56,6 +69,20 @@ const SettingsNavigationContainer = ({ isInWelcomePage }: IProps) => {
|
||||
}}>
|
||||
{ SettingsScreen }
|
||||
</SettingsStack.Screen>
|
||||
<SettingsStack.Screen
|
||||
component = { ProfileScreen }
|
||||
name = { screen.settings.profile }
|
||||
options = {{
|
||||
...profileSettingsScreenOptions,
|
||||
title: t('settingsView.profileSection')
|
||||
}} />
|
||||
<SettingsStack.Screen
|
||||
component = { LanguageSelectScreen }
|
||||
name = { screen.settings.language }
|
||||
options = {{
|
||||
...languageSelectScreenOptions,
|
||||
title: t('settings.language')
|
||||
}} />
|
||||
</SettingsStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
@@ -49,7 +49,11 @@ export function screenHeaderCloseButton(goBack: (e?: GestureResponderEvent | Rea
|
||||
* {@code true}; otherwise, {@code false}.
|
||||
*/
|
||||
export function isPrejoinPageEnabled(stateful: IStateful) {
|
||||
return getFeatureFlag(toState(stateful), PREJOIN_PAGE_ENABLED, true);
|
||||
const state = toState(stateful);
|
||||
const { prejoinConfig } = state['features/base/config'];
|
||||
const isPrejoinEnabledInConfig = prejoinConfig?.enabled;
|
||||
|
||||
return getFeatureFlag(state, PREJOIN_PAGE_ENABLED, isPrejoinEnabledInConfig ?? true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,7 +44,9 @@ export const screen = {
|
||||
help: 'Help',
|
||||
privacy: 'Privacy',
|
||||
terms: 'Terms'
|
||||
}
|
||||
},
|
||||
profile: 'Profile',
|
||||
language: 'Language'
|
||||
},
|
||||
unsafeRoomWarning: 'Unsafe Room Warning'
|
||||
};
|
||||
|
||||
@@ -196,6 +196,24 @@ export const preJoinScreenOptions = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for profile setting.
|
||||
*/
|
||||
export const profileSettingsScreenOptions = {
|
||||
headerStyle: {
|
||||
backgroundColor: BaseTheme.palette.ui01
|
||||
},
|
||||
headerTitleStyle: {
|
||||
color: BaseTheme.palette.text01
|
||||
},
|
||||
headerBackTitleVisible: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Screen options for language select screen.
|
||||
*/
|
||||
export const languageSelectScreenOptions = profileSettingsScreenOptions;
|
||||
|
||||
/**
|
||||
* Screen options for pre-join screen.
|
||||
*/
|
||||
|
||||
@@ -20,10 +20,10 @@ export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomNam
|
||||
const okDisabled = !roomName;
|
||||
const dispatch = useDispatch();
|
||||
const onBreakoutRoomNameChange = useCallback((newRoomName: string) => {
|
||||
setRoomName(newRoomName?.trim());
|
||||
setRoomName(newRoomName);
|
||||
}, [ setRoomName ]);
|
||||
const onSubmit = useCallback(() => {
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName));
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName?.trim()));
|
||||
}, [ breakoutRoomJid, dispatch, roomName ]);
|
||||
|
||||
return (<Dialog
|
||||
|
||||
@@ -85,7 +85,8 @@ export default {
|
||||
position: 'absolute',
|
||||
right: -3,
|
||||
top: -3,
|
||||
textAlign: 'center'
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 2
|
||||
},
|
||||
|
||||
participantsButtonBadge: {
|
||||
|
||||
@@ -30,6 +30,33 @@ import MeetingParticipants from './MeetingParticipants';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
participantsPane: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width .16s ease-in-out',
|
||||
width: '315px',
|
||||
zIndex: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
fontWeight: 600,
|
||||
height: '100%',
|
||||
|
||||
[[ '& > *:first-child', '& > *:last-child' ] as any]: {
|
||||
flexShrink: 0
|
||||
},
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
|
||||
container: {
|
||||
boxSizing: 'border-box',
|
||||
flex: 1,
|
||||
@@ -87,7 +114,7 @@ const useStyles = makeStyles()(theme => {
|
||||
});
|
||||
|
||||
const ParticipantsPane = () => {
|
||||
const { classes } = useStyles();
|
||||
const { classes, cx } = useStyles();
|
||||
const paneOpen = useSelector(getParticipantsPaneOpen);
|
||||
const isBreakoutRoomsSupported = useSelector((state: IReduxState) => state['features/base/conference'])
|
||||
.conference?.getBreakoutRooms()?.isSupported();
|
||||
@@ -136,49 +163,47 @@ const ParticipantsPane = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'participants_pane'>
|
||||
<div className = 'participants_pane-content'>
|
||||
<div className = { classes.header }>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('participantsPane.close', 'Close') }
|
||||
icon = { IconCloseLarge }
|
||||
onClick = { onClosePane } />
|
||||
</div>
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
{showMuteAllButton && (
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
labelKey = { 'participantsPane.actions.muteAll' }
|
||||
onClick = { onMuteAll }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
{showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
icon = { IconDotsHorizontal }
|
||||
id = 'participants-pane-context-menu'
|
||||
onClick = { onToggleContext }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className = { cx('participants_pane', classes.participantsPane) }>
|
||||
<div className = { classes.header }>
|
||||
<ClickableIcon
|
||||
accessibilityLabel = { t('participantsPane.close', 'Close') }
|
||||
icon = { IconCloseLarge }
|
||||
onClick = { onClosePane } />
|
||||
</div>
|
||||
<div className = { classes.container }>
|
||||
<LobbyParticipants />
|
||||
<br className = { classes.antiCollapse } />
|
||||
<MeetingParticipants
|
||||
searchString = { searchString }
|
||||
setSearchString = { setSearchString } />
|
||||
{isBreakoutRoomsSupported && <RoomList searchString = { searchString } />}
|
||||
{showAddRoomButton && <AddBreakoutRoomButton />}
|
||||
</div>
|
||||
{showFooter && (
|
||||
<div className = { classes.footer }>
|
||||
{showMuteAllButton && (
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.muteAll') }
|
||||
labelKey = { 'participantsPane.actions.muteAll' }
|
||||
onClick = { onMuteAll }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
)}
|
||||
{showMoreActionsButton && (
|
||||
<div className = { classes.footerMoreContainer }>
|
||||
<Button
|
||||
accessibilityLabel = { t('participantsPane.actions.moreModerationActions') }
|
||||
icon = { IconDotsHorizontal }
|
||||
id = 'participants-pane-context-menu'
|
||||
onClick = { onToggleContext }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
<FooterContextMenu
|
||||
isOpen = { contextOpen }
|
||||
onDrawerClose = { onDrawerClose }
|
||||
onMouseLeave = { onToggleContext } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user