mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-09-09 18:38:39 +00:00
Compare commits
34 Commits
8090
...
jitsi-meet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d2bae6ec1 | ||
|
|
fa6dc292c1 | ||
|
|
b5ac40b32a | ||
|
|
8299aa498b | ||
|
|
ce22adfe64 | ||
|
|
01bcccdd99 | ||
|
|
673a54adb1 | ||
|
|
f48c03e314 | ||
|
|
7641ddad68 | ||
|
|
26021b2dd0 | ||
|
|
a6457db819 | ||
|
|
8bfa65987d | ||
|
|
b1c0cc5322 | ||
|
|
67cbef0d7a | ||
|
|
b2e06c1c94 | ||
|
|
70a05e487e | ||
|
|
c2a446a79b | ||
|
|
56df23af35 | ||
|
|
1c12f5cf2a | ||
|
|
06b727ae1a | ||
|
|
a2aca30d57 | ||
|
|
68106feb57 | ||
|
|
3066fc1d31 | ||
|
|
4834fb7b6f | ||
|
|
1e101afe5a | ||
|
|
4d79bbb5d8 | ||
|
|
e31aff5afd | ||
|
|
7bc9913b29 | ||
|
|
2483d901d6 | ||
|
|
6ff7995cee | ||
|
|
5d563402d0 | ||
|
|
6727004930 | ||
|
|
c04000ea20 | ||
|
|
23be14697c |
4
Makefile
4
Makefile
@@ -24,9 +24,9 @@ else
|
||||
WEBPACK_DEV_SERVER = ./node_modules/.bin/webpack serve --mode development
|
||||
endif
|
||||
|
||||
all: compile deploy clean
|
||||
all: compile deploy
|
||||
|
||||
compile:
|
||||
compile: clean
|
||||
NODE_OPTIONS=--max-old-space-size=8192 \
|
||||
$(WEBPACK)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
JSC_VERSION="r"$(jq -r '.dependencies."jsc-android"' ${THIS_DIR}/../../node_modules/react-native/package.json | cut -d . -f 1 | cut -c 2-)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
MVN_HTTP=1
|
||||
@@ -67,15 +66,12 @@ pushd ${THIS_DIR}/../
|
||||
./gradlew publish
|
||||
popd
|
||||
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
if [[ $MVN_HTTP == 0 ]]; then
|
||||
pushd ${MVN_REPO_PATH}
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
|
||||
30
config.js
30
config.js
@@ -232,9 +232,26 @@ var config = {
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 720.
|
||||
// resolution: 720,
|
||||
|
||||
// DEPRECATED. Please use raisedHands.disableRemoveRaisedHandOnFocus instead.
|
||||
// Specifies whether the raised hand will hide when someone becomes a dominant speaker or not
|
||||
// disableRemoveRaisedHandOnFocus: false,
|
||||
|
||||
// Specifies which raised hand related config should be set.
|
||||
// raisedHands: {
|
||||
// // Specifies whether the raised hand can be lowered by moderator.
|
||||
// disableLowerHandByModerator: false,
|
||||
|
||||
// // Specifies whether there is a notification before hiding the raised hand
|
||||
// // when someone becomes the dominant speaker.
|
||||
// disableLowerHandNotification: true,
|
||||
|
||||
// // Specifies whether there is a notification when you are the next speaker in line.
|
||||
// disableNextSpeakerNotification: false,
|
||||
|
||||
// // Specifies whether the raised hand will hide when someone becomes a dominant speaker or not.
|
||||
// disableRemoveRaisedHandOnFocus: false,
|
||||
// },
|
||||
|
||||
// speakerStats: {
|
||||
// // Specifies whether the speaker stats is enable or not.
|
||||
// disabled: false,
|
||||
@@ -1549,6 +1566,17 @@ var config = {
|
||||
// and will automatically redirect to the token service to get the token for the meeting.
|
||||
// tokenAuthUrlAutoRedirect: false
|
||||
|
||||
// You can put an array of values to target different entity types in the invite dialog.
|
||||
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"
|
||||
// peopleSearchQueryTypes: ["user", "email"],
|
||||
// Directory endpoint which is called for invite dialog autocomplete
|
||||
// peopleSearchUrl: "https://myservice.com/api/people",
|
||||
// Endpoint which is called to send invitation requests
|
||||
// inviteServiceUrl: "https://myservice.com/api/invite",
|
||||
|
||||
// For external entities (e. g. email), the localStorage key holding the token value for directory authentication
|
||||
// peopleSearchTokenLocation: "mytoken",
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
@@ -1565,8 +1593,6 @@ var config = {
|
||||
iAmRecorder
|
||||
iAmSipGateway
|
||||
microsoftApiApplicationClientID
|
||||
peopleSearchQueryTypes
|
||||
peopleSearchUrl
|
||||
requireDisplayName
|
||||
*/
|
||||
|
||||
|
||||
@@ -69,6 +69,11 @@
|
||||
|
||||
window.indexLoadedTime = window.performance.now();
|
||||
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
|
||||
window.addEventListener('load', function() {
|
||||
window.loadedEventTime = window.performance.now();
|
||||
console.log("(TIME) window loaded event:\t", loadedEventTime);
|
||||
});
|
||||
|
||||
// XXX the code below listeners for errors and displays an error message
|
||||
// in the document body when any of the required files fails to load.
|
||||
// The intention is to prevent from displaying broken page.
|
||||
|
||||
@@ -1022,7 +1022,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-performance (5.0.0):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.7.1):
|
||||
- react-native-safe-area-context (4.10.8):
|
||||
- React-Core
|
||||
- react-native-slider (4.4.3):
|
||||
- React-Core
|
||||
@@ -1214,7 +1214,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNDeviceInfo (10.9.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.17.1):
|
||||
- RNGestureHandler (2.18.1):
|
||||
- glog
|
||||
- RCT-Folly (= 2022.05.16.00)
|
||||
- React-Core
|
||||
@@ -1558,7 +1558,7 @@ SPEC CHECKSUMS:
|
||||
react-native-orientation-locker: 4409c5b12b65f942e75449872b4f078b6f27af81
|
||||
react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df
|
||||
react-native-performance: 47ac22ebf2aa24f324a96a5825581f6ce18c09e8
|
||||
react-native-safe-area-context: 9697629f7b2cda43cf52169bb7e0767d330648c2
|
||||
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
|
||||
react-native-slider: 1cdd6ba29675df21f30544253bf7351d3c2d68c4
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
react-native-video: 472b7c366eaaaa0207e546d9a50410df89790bcf
|
||||
@@ -1589,7 +1589,7 @@ SPEC CHECKSUMS:
|
||||
RNCClipboard: 0a720adef5ec193aa0e3de24c3977222c7e52a37
|
||||
RNDefaultPreference: 08bdb06cfa9188d5da97d4642dac745218d7fb31
|
||||
RNDeviceInfo: 02ea8b23e2280fa18e00a06d7e62804d74028579
|
||||
RNGestureHandler: 67d3f1f69d4d0c98d6e83f4229e3bbf997d1dc72
|
||||
RNGestureHandler: 1155b1898ceddefeebf77792927360d44fe11e77
|
||||
RNGoogleSignin: a6a612cce56a45ab701c5c5c6e36f5390522d100
|
||||
RNScreens: e842cdccb23c0a084bd6307f6fa83fd1c1738029
|
||||
RNSound: 6c156f925295bdc83e8e422e7d8b38d33bc71852
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -u
|
||||
set -e -u -x
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
|
||||
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
|
||||
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Lite-Info.plist)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
|
||||
echo "Releasing Jitsi Meet SDK Lite ${SDK_VERSION}"
|
||||
|
||||
@@ -50,9 +48,6 @@ xcodebuild -create-xcframework \
|
||||
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
|
||||
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
|
||||
-output ios/sdk/out/JitsiMeetSDK.xcframework
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git tag ios-sdk-lite-${SDK_VERSION}
|
||||
fi
|
||||
popd
|
||||
|
||||
pushd ${RELEASE_REPO}
|
||||
@@ -61,11 +56,9 @@ pushd ${RELEASE_REPO}
|
||||
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework lite/Frameworks/
|
||||
|
||||
# Add all files to git
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION} lite"
|
||||
git tag "${SDK_VERSION}-lite"
|
||||
fi
|
||||
git add -A .
|
||||
git commit --allow-empty -m "${SDK_VERSION} lite"
|
||||
git tag "${SDK_VERSION}-lite"
|
||||
|
||||
popd
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -u
|
||||
set -e -u -x
|
||||
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
|
||||
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
|
||||
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
|
||||
@@ -50,9 +48,6 @@ xcodebuild -create-xcframework \
|
||||
-framework ios/sdk/out/ios-device.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
|
||||
-framework ios/sdk/out/ios-simulator.xcarchive/Products/Library/Frameworks/JitsiMeetSDK.framework \
|
||||
-output ios/sdk/out/JitsiMeetSDK.xcframework
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
fi
|
||||
popd
|
||||
|
||||
pushd ${RELEASE_REPO}
|
||||
@@ -61,11 +56,9 @@ pushd ${RELEASE_REPO}
|
||||
cp -a ${PROJECT_REPO}/ios/sdk/out/JitsiMeetSDK.xcframework Frameworks/
|
||||
|
||||
# Add all files to git
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION}"
|
||||
git tag ${SDK_VERSION}
|
||||
fi
|
||||
git add -A .
|
||||
git commit --allow-empty -m "${SDK_VERSION}"
|
||||
git tag "${SDK_VERSION}"
|
||||
|
||||
popd
|
||||
|
||||
|
||||
@@ -786,6 +786,7 @@
|
||||
"newDeviceAction": "Use",
|
||||
"newDeviceAudioTitle": "New audio device detected",
|
||||
"newDeviceCameraTitle": "New camera detected",
|
||||
"nextToSpeak": "You are the next in line to speak",
|
||||
"noiseSuppressionDesktopAudioDescription": "Noise suppression can't be enabled while sharing desktop audio, please disable it and try again.",
|
||||
"noiseSuppressionFailedTitle": "Failed to start noise suppression",
|
||||
"noiseSuppressionStereoDescription": "Stereo audio noise suppression is not currently supported.",
|
||||
@@ -820,6 +821,7 @@
|
||||
"videoUnmuteBlockedDescription": "Camera unmute and desktop sharing operation have been temporarily blocked because of system limits.",
|
||||
"videoUnmuteBlockedTitle": "Camera unmute and desktop sharing blocked!",
|
||||
"viewLobby": "View lobby",
|
||||
"viewParticipants": "View participants",
|
||||
"viewVisitors": "View visitors",
|
||||
"waitingParticipants": "{{waitingParticipants}} people",
|
||||
"waitingVisitors": "Visitors waiting in queue: {{waitingVisitors}}",
|
||||
@@ -1502,7 +1504,6 @@
|
||||
"labelTooltip": "Number of visitors: {{count}}",
|
||||
"notification": {
|
||||
"demoteDescription": "Sent here by {{actor}}, raise your hand to participate",
|
||||
"description": "To participate raise your hand",
|
||||
"noMainParticipantsDescription": "A participant needs to start the meeting. Please try again in a bit.",
|
||||
"noMainParticipantsTitle": "This meeting hasn’t started yet.",
|
||||
"noVisitorLobby": "You cannot join while there is a lobby enabled for the meeting.",
|
||||
|
||||
@@ -1338,14 +1338,14 @@ class API {
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyReceivedChatMessage(
|
||||
{ body, id, nick, privateMessage, ts } = {}) {
|
||||
if (APP.conference.isLocalId(id)) {
|
||||
{ body, from, nick, privateMessage, ts } = {}) {
|
||||
if (APP.conference.isLocalId(from)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendEvent({
|
||||
name: 'incoming-message',
|
||||
from: id,
|
||||
from,
|
||||
message: body,
|
||||
nick,
|
||||
privateMessage,
|
||||
@@ -1814,9 +1814,9 @@ class API {
|
||||
* Notify external application of a participant, remote or local, being
|
||||
* removed from the conference by another participant.
|
||||
*
|
||||
* @param {string} kicked - The ID of the participant removed from the
|
||||
* @param {Object} kicked - The participant removed from the
|
||||
* conference.
|
||||
* @param {string} kicker - The ID of the participant that removed the
|
||||
* @param {Object} kicker - The participant that removed the
|
||||
* other participant.
|
||||
* @returns {void}
|
||||
*/
|
||||
|
||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -61,7 +61,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1844.0.0+a9b6dd7e/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1847.0.0+03eef6de/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -82,15 +82,15 @@
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.17.1",
|
||||
"react-native-gesture-handler": "2.18.1",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-immersive-mode": "2.0.2",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.6.0",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-safe-area-context": "4.10.8",
|
||||
"react-native-screens": "3.32.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
@@ -12489,8 +12489,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1844.0.0+a9b6dd7e/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-ZLOF2lmTgMU+eEltT7UyZ9LY16g+IjA4sWfdFafbTxKub99Db8/B56vfeN3AEXy4ldtslifhgP2Cx0x+CX3Esg==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1847.0.0+03eef6de/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-HnJCDEFRNFQcgG3JeF7+xYrAdwYQt/HZwe9QhuMhEMXOiL2oSj/prWuAWTq1Ew2OtGKQVHEO8ntfYnG9M9vy6g==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -15307,9 +15307,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.17.1.tgz",
|
||||
"integrity": "sha512-pWfniN6NuVKUq40KACuD3NCMe+bWNQCpD3cmxL6aLSCTwPKYmf7l4Lp0/E/almpjvxhybJZtFLU0w4tmxnIKaA==",
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.18.1.tgz",
|
||||
"integrity": "sha512-WF2fxQ5kTaxHghlkBM4YxO86SyGWVwrSNgJ1E8z/ZtL2xD5B3bg5agvuVFfOzvceC114yq71s6E9vKPz94ZxRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
@@ -15334,9 +15334,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-immersive-mode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.1.tgz",
|
||||
"integrity": "sha512-2wlL7VIHl4rr4gwgnUp9K1UvsN7J5VCGqoAvBWQXvB4xn7XaoDEl6z9vqaqOiEdC6aAh2d/7zqcJz+dfcR2ELw==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.2.tgz",
|
||||
"integrity": "sha512-W4YBpf/seURr6pdt91bKWtlQhwoZYgQN1nllj/y+EkwKs5xbu/IiRxlCs/8ML7dyA8wbOFaXR24AC4HOrvxapw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-native": ">=0.60.5"
|
||||
}
|
||||
@@ -15350,6 +15351,7 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-orientation-locker/-/react-native-orientation-locker-1.6.0.tgz",
|
||||
"integrity": "sha512-D3IOtAcaAi6S2w0Y1EUnr16I47isosQbE7F67fAu9A+gE67NkyKaJ9HL5EsZ+Uc7+7m+NsuBjx3dxuANNy8rVA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.13.1",
|
||||
"react-native": ">=0.63.2",
|
||||
@@ -15365,6 +15367,7 @@
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.2.0.tgz",
|
||||
"integrity": "sha512-pf9OnL/Tkr+5s4Gjmsn7xh91PtJLDa6qxYa/bmtUhd/+s4cQdWQ8DIFoOFghwZIHHHwVdWtoXkp6HtpjN+r20g==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
@@ -15395,9 +15398,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.7.1.tgz",
|
||||
"integrity": "sha512-X2pJG2ttmAbiGlItWedvDkZg1T1ikmEDiz+7HsiIwAIm2UbFqlhqn+B1JF53mSxPzdNaDcCQVHRNPvj8oFu6Yg==",
|
||||
"version": "4.10.8",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.8.tgz",
|
||||
"integrity": "sha512-Jx1lovhvIdYygg0UsMCBUJN0Wvj9GlA5bbcBLzjZf93uJpNHzaiHC4hR280+sNVK1+/pMHEyEkXVHDZE5JWn0w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
@@ -27986,8 +27990,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1844.0.0+a9b6dd7e/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-ZLOF2lmTgMU+eEltT7UyZ9LY16g+IjA4sWfdFafbTxKub99Db8/B56vfeN3AEXy4ldtslifhgP2Cx0x+CX3Esg==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1847.0.0+03eef6de/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-HnJCDEFRNFQcgG3JeF7+xYrAdwYQt/HZwe9QhuMhEMXOiL2oSj/prWuAWTq1Ew2OtGKQVHEO8ntfYnG9M9vy6g==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.2.1",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
@@ -30105,9 +30109,9 @@
|
||||
"integrity": "sha512-MKbuBbovO8eGiAM9i6o0nrdBXivhRpzPQ+aVBXGJEPMH7RrCSNUKaCoEpkjfGHlTxjZimi6WjDCjjzCRSHlV1A=="
|
||||
},
|
||||
"react-native-gesture-handler": {
|
||||
"version": "2.17.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.17.1.tgz",
|
||||
"integrity": "sha512-pWfniN6NuVKUq40KACuD3NCMe+bWNQCpD3cmxL6aLSCTwPKYmf7l4Lp0/E/almpjvxhybJZtFLU0w4tmxnIKaA==",
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.18.1.tgz",
|
||||
"integrity": "sha512-WF2fxQ5kTaxHghlkBM4YxO86SyGWVwrSNgJ1E8z/ZtL2xD5B3bg5agvuVFfOzvceC114yq71s6E9vKPz94ZxRw==",
|
||||
"requires": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
@@ -30124,9 +30128,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-immersive-mode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.1.tgz",
|
||||
"integrity": "sha512-2wlL7VIHl4rr4gwgnUp9K1UvsN7J5VCGqoAvBWQXvB4xn7XaoDEl6z9vqaqOiEdC6aAh2d/7zqcJz+dfcR2ELw=="
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-immersive-mode/-/react-native-immersive-mode-2.0.2.tgz",
|
||||
"integrity": "sha512-W4YBpf/seURr6pdt91bKWtlQhwoZYgQN1nllj/y+EkwKs5xbu/IiRxlCs/8ML7dyA8wbOFaXR24AC4HOrvxapw=="
|
||||
},
|
||||
"react-native-keep-awake": {
|
||||
"version": "4.0.0",
|
||||
@@ -30159,9 +30163,9 @@
|
||||
"integrity": "sha512-nZJdliNFaA7vmu9PbYGSlwguRQVsuWX8CtchUUxZvjd/i9B3OYYveajIcuic3oQjWyTydLeDlhXRwpOUlj2Npw=="
|
||||
},
|
||||
"react-native-safe-area-context": {
|
||||
"version": "4.7.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.7.1.tgz",
|
||||
"integrity": "sha512-X2pJG2ttmAbiGlItWedvDkZg1T1ikmEDiz+7HsiIwAIm2UbFqlhqn+B1JF53mSxPzdNaDcCQVHRNPvj8oFu6Yg=="
|
||||
"version": "4.10.8",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-4.10.8.tgz",
|
||||
"integrity": "sha512-Jx1lovhvIdYygg0UsMCBUJN0Wvj9GlA5bbcBLzjZf93uJpNHzaiHC4hR280+sNVK1+/pMHEyEkXVHDZE5JWn0w=="
|
||||
},
|
||||
"react-native-screens": {
|
||||
"version": "3.32.0",
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1844.0.0+a9b6dd7e/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1847.0.0+03eef6de/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -88,15 +88,15 @@
|
||||
"react-native-default-preference": "1.4.4",
|
||||
"react-native-device-info": "10.9.0",
|
||||
"react-native-dialog": "https://github.com/jitsi/react-native-dialog/releases/download/v9.2.2-jitsi.1/react-native-dialog-9.2.2.tgz",
|
||||
"react-native-gesture-handler": "2.17.1",
|
||||
"react-native-gesture-handler": "2.18.1",
|
||||
"react-native-get-random-values": "1.9.0",
|
||||
"react-native-immersive-mode": "2.0.1",
|
||||
"react-native-immersive-mode": "2.0.2",
|
||||
"react-native-keep-awake": "4.0.0",
|
||||
"react-native-orientation-locker": "1.6.0",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "5.10.3",
|
||||
"react-native-performance": "5.0.0",
|
||||
"react-native-safe-area-context": "4.7.1",
|
||||
"react-native-safe-area-context": "4.10.8",
|
||||
"react-native-screens": "3.32.0",
|
||||
"react-native-sound": "0.11.2",
|
||||
"react-native-splash-screen": "3.3.0",
|
||||
|
||||
@@ -48,6 +48,11 @@ export interface IProps {
|
||||
*/
|
||||
colorBase?: string;
|
||||
|
||||
/**
|
||||
* Indicates the default icon for the avatar.
|
||||
*/
|
||||
defaultIcon?: string;
|
||||
|
||||
/**
|
||||
* Display name of the entity to render an avatar for (if any). This is handy when we need
|
||||
* an avatar for a non-participant entity (e.g. A recent list item).
|
||||
@@ -112,6 +117,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
|
||||
* @static
|
||||
*/
|
||||
static defaultProps = {
|
||||
defaultIcon: IconUser,
|
||||
dynamicColor: true
|
||||
};
|
||||
|
||||
@@ -172,6 +178,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
|
||||
_loadableAvatarUrlUseCORS,
|
||||
className,
|
||||
colorBase,
|
||||
defaultIcon,
|
||||
dynamicColor,
|
||||
id,
|
||||
size,
|
||||
@@ -229,7 +236,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
|
||||
}
|
||||
|
||||
if (navigator.product !== 'ReactNative') {
|
||||
avatarProps.iconUser = IconUser;
|
||||
avatarProps.iconUser = defaultIcon;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -477,6 +477,7 @@ export interface IConfig {
|
||||
};
|
||||
pcStatsInterval?: number;
|
||||
peopleSearchQueryTypes?: string[];
|
||||
peopleSearchTokenLocation?: string;
|
||||
peopleSearchUrl?: string;
|
||||
preferBosh?: boolean;
|
||||
preferVisitor?: boolean;
|
||||
@@ -487,6 +488,12 @@ export interface IConfig {
|
||||
hideExtraJoinButtons?: Array<string>;
|
||||
};
|
||||
prejoinPageEnabled?: boolean;
|
||||
raisedHands?: {
|
||||
disableLowerHandByModerator?: boolean;
|
||||
disableLowerHandNotification?: boolean;
|
||||
disableNextSpeakerNotification?: boolean;
|
||||
disableRemoveRaisedHandOnFocus?: boolean;
|
||||
};
|
||||
readOnlyName?: boolean;
|
||||
recordingLimit?: {
|
||||
appName?: string;
|
||||
|
||||
@@ -201,6 +201,7 @@ export default [
|
||||
'preferVisitor',
|
||||
'prejoinConfig',
|
||||
'prejoinPageEnabled',
|
||||
'raisedHands',
|
||||
'recordingService',
|
||||
'requireDisplayName',
|
||||
'remoteVideoMenu',
|
||||
|
||||
@@ -6,6 +6,7 @@ import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getLocalParticipant } from '../participants/functions';
|
||||
import { parseURLParams } from '../util/parseURLParams';
|
||||
|
||||
import { IConfig } from './configType';
|
||||
@@ -88,7 +89,37 @@ export function getFeatureFlag(state: IReduxState, featureFlag: string) {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableRemoveRaisedHandOnFocus(state: IReduxState) {
|
||||
return state['features/base/config']?.disableRemoveRaisedHandOnFocus || false;
|
||||
return state['features/base/config']?.raisedHands?.disableRemoveRaisedHandOnFocus || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableLowerHandByModerator.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableLowerHandByModerator(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableLowerHandByModerator || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableLowerHandNotification.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableLowerHandNotification(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableLowerHandNotification || true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector used to get the disableNextSpeakerNotification.
|
||||
*
|
||||
* @param {Object} state - The global state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function getDisableNextSpeakerNotification(state: IReduxState) {
|
||||
return state['features/base/config']?.raisedHands?.disableNextSpeakerNotification || false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,6 +215,31 @@ export function isNameReadOnly(state: IReduxState): boolean {
|
||||
|| state['features/base/config'].readOnlyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the participant is the next one in the queue to speak.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isNextToSpeak(state: IReduxState): boolean {
|
||||
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue || [];
|
||||
const participantId = getLocalParticipant(state)?.id;
|
||||
|
||||
return participantId === raisedHandsQueue[0]?.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the next to speak participant in the queue has been notified.
|
||||
*
|
||||
* @param {Object} state - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasBeenNotified(state: IReduxState): boolean {
|
||||
const raisedHandsQueue = state['features/base/participants'].raisedHandsQueue;
|
||||
|
||||
return Boolean(raisedHandsQueue[0]?.hasBeenNotified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selector for determining if the display name is visible.
|
||||
*
|
||||
|
||||
@@ -442,6 +442,12 @@ function _translateLegacyConfig(oldValue: IConfig) {
|
||||
newValue.disabledSounds.unshift('INCOMING_MSG_SOUND');
|
||||
}
|
||||
|
||||
newValue.raisedHands = newValue.raisedHands || {};
|
||||
|
||||
if (oldValue.disableRemoveRaisedHandOnFocus) {
|
||||
newValue.raisedHands.disableRemoveRaisedHandOnFocus = oldValue.disableRemoveRaisedHandOnFocus;
|
||||
}
|
||||
|
||||
if (oldValue.stereo || oldValue.opusMaxAverageBitrate) {
|
||||
newValue.audioQuality = {
|
||||
opusMaxAverageBitrate: oldValue.audioQuality?.opusMaxAverageBitrate ?? oldValue.opusMaxAverageBitrate,
|
||||
|
||||
@@ -59,7 +59,7 @@ export default {
|
||||
function normalizeLanguage(language: string) {
|
||||
const [ lang, variant ] = language.replace('_', '-').split('-');
|
||||
|
||||
if (!variant || lang === variant) {
|
||||
if (!variant || lang.toUpperCase() === variant.toUpperCase()) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export function getJwtName(state: IReduxState) {
|
||||
* @param {string} feature - The feature we want to check.
|
||||
* @param {boolean} ifNoToken - Default value if there is no token.
|
||||
* @param {boolean} ifNotInFeatures - Default value if features prop exists but does not have the {@code feature}.
|
||||
* @returns {bolean}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isJwtFeatureEnabled(state: IReduxState, feature: string, ifNoToken = false, ifNotInFeatures = false) {
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Create an action to mark the participant as notified to speak next.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFIED_TO_SPEAK
|
||||
* }
|
||||
*/
|
||||
export const NOTIFIED_TO_SPEAK = 'NOTIFIED_TO_SPEAK';
|
||||
|
||||
/**
|
||||
* Create an action for when dominant speaker changes.
|
||||
*
|
||||
|
||||
@@ -544,23 +544,27 @@ export function createVirtualScreenshareParticipant(sourceName: string, local: b
|
||||
*/
|
||||
export function participantKicked(kicker: any, kicked: any) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const kickedId = kicked.getId();
|
||||
const kickerId = kicker.getId();
|
||||
|
||||
dispatch({
|
||||
type: PARTICIPANT_KICKED,
|
||||
kicked: kicked.getId(),
|
||||
kicker: kicker?.getId()
|
||||
kicked: kickedId,
|
||||
kicker: kickerId
|
||||
});
|
||||
|
||||
if (kicked.isReplaced?.()) {
|
||||
if (kicked.isReplaced?.() || kickerId === localParticipant?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleArguments: {
|
||||
kicked:
|
||||
getParticipantDisplayName(getState, kicked.getId()),
|
||||
getParticipantDisplayName(state, kickedId),
|
||||
kicker:
|
||||
getParticipantDisplayName(getState, kicker.getId())
|
||||
getParticipantDisplayName(state, kickerId)
|
||||
},
|
||||
titleKey: 'notify.kickParticipant'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
RAISE_HAND_NOTIFICATION_ID
|
||||
} from '../../notifications/constants';
|
||||
import { open as openParticipantsPane } from '../../participants-pane/actions';
|
||||
import { isForceMuted } from '../../participants-pane/functions';
|
||||
import { CALLING, INVITED } from '../../presence-status/constants';
|
||||
import { RAISE_HAND_SOUND_ID } from '../../reactions/constants';
|
||||
@@ -783,10 +784,19 @@ function _raiseHandUpdated({ dispatch, getState }: IStore, conference: IJitsiCon
|
||||
|| isForceMuted(participant, MEDIA_TYPE.VIDEO, state);
|
||||
}
|
||||
|
||||
const action = shouldDisplayAllowAction ? {
|
||||
customActionNameKey: [ 'notify.allowAction' ],
|
||||
customActionHandler: [ () => dispatch(approveParticipant(participantId)) ]
|
||||
} : {};
|
||||
let action;
|
||||
|
||||
if (shouldDisplayAllowAction) {
|
||||
action = {
|
||||
customActionNameKey: [ 'notify.allowAction' ],
|
||||
customActionHandler: [ () => dispatch(approveParticipant(participantId)) ]
|
||||
};
|
||||
} else {
|
||||
action = {
|
||||
customActionNameKey: [ 'notify.viewParticipants' ],
|
||||
customActionHandler: [ () => dispatch(openParticipantsPane()) ]
|
||||
};
|
||||
}
|
||||
|
||||
if (raisedHandTimestamp) {
|
||||
let notificationTitle;
|
||||
@@ -810,7 +820,7 @@ function _raiseHandUpdated({ dispatch, getState }: IStore, conference: IJitsiCon
|
||||
concatText: true,
|
||||
uid: RAISE_HAND_NOTIFICATION_ID,
|
||||
...action
|
||||
}, shouldDisplayAllowAction ? NOTIFICATION_TIMEOUT_TYPE.MEDIUM : NOTIFICATION_TIMEOUT_TYPE.SHORT));
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch(playSound(RAISE_HAND_SOUND_ID));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { set } from '../redux/functions';
|
||||
|
||||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
NOTIFIED_TO_SPEAK,
|
||||
OVERWRITE_PARTICIPANT_NAME,
|
||||
PARTICIPANT_ID_CHANGED,
|
||||
PARTICIPANT_JOINED,
|
||||
@@ -92,7 +93,7 @@ export interface IParticipantsState {
|
||||
numberOfParticipantsNotSupportingE2EE: number;
|
||||
overwrittenNameList: { [id: string]: string; };
|
||||
pinnedParticipant?: string;
|
||||
raisedHandsQueue: Array<{ id: string; raisedHandTimestamp: number; }>;
|
||||
raisedHandsQueue: Array<{ hasBeenNotified?: boolean; id: string; raisedHandTimestamp: number; }>;
|
||||
remote: Map<string, IParticipant>;
|
||||
remoteVideoSources: Set<string>;
|
||||
sortedRemoteParticipants: Map<string, string>;
|
||||
@@ -114,6 +115,23 @@ export interface IParticipantsState {
|
||||
ReducerRegistry.register<IParticipantsState>('features/base/participants',
|
||||
(state = DEFAULT_STATE, action): IParticipantsState => {
|
||||
switch (action.type) {
|
||||
case NOTIFIED_TO_SPEAK: {
|
||||
return {
|
||||
...state,
|
||||
raisedHandsQueue: state.raisedHandsQueue.map((item, index) => {
|
||||
if (index === 0) {
|
||||
|
||||
return {
|
||||
...item,
|
||||
hasBeenNotified: true
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
case PARTICIPANT_ID_CHANGED: {
|
||||
const { local } = state;
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { hideNotification, showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, RAISE_HAND_NOTIFICATION_ID } from '../../notifications/constants';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { getSsrcRewritingFeatureFlag } from '../config/functions.any';
|
||||
import {
|
||||
getDisableNextSpeakerNotification,
|
||||
getSsrcRewritingFeatureFlag,
|
||||
hasBeenNotified,
|
||||
isNextToSpeak } from '../config/functions.any';
|
||||
import { VIDEO_TYPE } from '../media/constants';
|
||||
import StateListenerRegistry from '../redux/StateListenerRegistry';
|
||||
|
||||
import { NOTIFIED_TO_SPEAK } from './actionTypes';
|
||||
import { createVirtualScreenshareParticipant, participantLeft } from './actions';
|
||||
import {
|
||||
getParticipantById,
|
||||
@@ -25,6 +34,21 @@ StateListenerRegistry.register(
|
||||
&& _updateScreenshareParticipantsBasedOnPresence(store)
|
||||
);
|
||||
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/participants'].raisedHandsQueue,
|
||||
/* listener */ (raisedHandsQueue, store) => {
|
||||
if (raisedHandsQueue.length
|
||||
&& isNextToSpeak(store.getState())
|
||||
&& !hasBeenNotified(store.getState())
|
||||
&& !getDisableNextSpeakerNotification(store.getState())) {
|
||||
_notifyNextSpeakerInRaisedHandQueue(store);
|
||||
}
|
||||
if (!raisedHandsQueue[0]) {
|
||||
store.dispatch(hideNotification(RAISE_HAND_NOTIFICATION_ID));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Compares the old and new screenshare lists provided and creates/removes the virtual screenshare participant
|
||||
* tiles accodingly.
|
||||
@@ -121,3 +145,23 @@ function _updateScreenshareParticipantsBasedOnPresence(store: IStore): void {
|
||||
|
||||
_createOrRemoveVirtualParticipants(previousScreenshareSourceNames, currentScreenshareSourceNames, store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles notifying the next speaker in the raised hand queue.
|
||||
*
|
||||
* @param {*} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _notifyNextSpeakerInRaisedHandQueue(store: IStore): void {
|
||||
const { dispatch } = store;
|
||||
|
||||
batch(() => {
|
||||
dispatch(showNotification({
|
||||
titleKey: 'notify.nextToSpeak',
|
||||
maxLines: 2
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
dispatch({
|
||||
type: NOTIFIED_TO_SPEAK
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,12 +83,12 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const { messages } = getState()['features/chat'];
|
||||
|
||||
messages?.forEach(m => {
|
||||
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.id)) {
|
||||
if (m.messageType === MESSAGE_TYPE_REMOTE && !getParticipantById(getState(), m.participantId)) {
|
||||
const rooms: IRooms = action.rooms;
|
||||
|
||||
for (const room of Object.values(rooms)) {
|
||||
const participants = room.participants || {};
|
||||
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.id));
|
||||
const matchedJid = Object.keys(participants).find(jid => jid.endsWith(m.participantId));
|
||||
|
||||
if (matchedJid) {
|
||||
m.displayName = participants[matchedJid].displayName;
|
||||
|
||||
@@ -36,13 +36,13 @@ export default class AbstractMessageContainer<P extends IProps, S> extends Compo
|
||||
for (let i = 0; i < messagesCount; i++) {
|
||||
const message = this.props.messages[i];
|
||||
|
||||
if (message.id === currentGroupParticipantId) {
|
||||
if (message.participantId === currentGroupParticipantId) {
|
||||
currentGrouping.push(message);
|
||||
} else {
|
||||
currentGrouping.length && groups.push(currentGrouping);
|
||||
|
||||
currentGrouping = [ message ];
|
||||
currentGroupParticipantId = message.id;
|
||||
currentGroupParticipantId = message.participantId;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Linkify from '../../../base/react/components/native/Linkify';
|
||||
import { isGifMessage } from '../../../gifs/functions.native';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import { CHAR_LIMIT, MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import {
|
||||
getCanReplyToMessage,
|
||||
getFormattedTimestamp,
|
||||
@@ -73,7 +73,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
messageBubbleStyle.push(styles.lobbyMessageBubble);
|
||||
}
|
||||
|
||||
const messageText = replaceNonUnicodeEmojis(getMessageText(this.props.message));
|
||||
const messageText = getMessageText(this.props.message);
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper as ViewStyle } >
|
||||
@@ -84,13 +84,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
{ this._renderDisplayName() }
|
||||
{ isGifMessage(messageText)
|
||||
? <GifMessage message = { messageText } />
|
||||
: (
|
||||
<Linkify
|
||||
linkStyle = { styles.chatLink }
|
||||
style = { styles.chatMessage }>
|
||||
{ messageText }
|
||||
</Linkify>
|
||||
)}
|
||||
: this._renderMessageTextComponent(messageText) }
|
||||
{ this._renderPrivateNotice() }
|
||||
</View>
|
||||
{ this._renderPrivateReplyButton() }
|
||||
@@ -104,7 +98,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
/**
|
||||
* Renders the avatar of the sender.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
* @returns {React.ReactElement<*>}
|
||||
*/
|
||||
_renderAvatar() {
|
||||
const { message } = this.props;
|
||||
@@ -113,7 +107,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
<View style = { styles.avatarWrapper }>
|
||||
{ this.props.showAvatar && <Avatar
|
||||
displayName = { message.displayName }
|
||||
participantId = { message.id }
|
||||
participantId = { message.participantId }
|
||||
size = { styles.avatarWrapper.width } />
|
||||
}
|
||||
</View>
|
||||
@@ -123,7 +117,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
/**
|
||||
* Renders the display name of the sender if necessary.
|
||||
*
|
||||
* @returns {React$Element<*> | null}
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
const { message, showDisplayName } = this.props;
|
||||
@@ -139,10 +133,37 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the message text based on number of characters.
|
||||
*
|
||||
* @param {string} messageText - The message text.
|
||||
* @returns {React.ReactElement<*>}
|
||||
*/
|
||||
_renderMessageTextComponent(messageText: string) {
|
||||
|
||||
if (messageText.length >= CHAR_LIMIT) {
|
||||
return (
|
||||
<Text
|
||||
selectable = { true }
|
||||
style = { styles.chatMessage }>
|
||||
{ messageText }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Linkify
|
||||
linkStyle = { styles.chatLink }
|
||||
style = { styles.chatMessage }>
|
||||
{ replaceNonUnicodeEmojis(messageText) }
|
||||
</Linkify>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the message privacy notice, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*> | null}
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderPrivateNotice() {
|
||||
const { message, knocking } = this.props;
|
||||
@@ -161,7 +182,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
/**
|
||||
* Renders the private reply button, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*> | null}
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderPrivateReplyButton() {
|
||||
const { message, canReply } = this.props;
|
||||
@@ -175,7 +196,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
<View style = { styles.replyContainer as ViewStyle }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { lobbyChat }
|
||||
participantID = { message.id }
|
||||
participantID = { message.participantId }
|
||||
reply = { true }
|
||||
showLabel = { false }
|
||||
toggledStyles = { styles.replyStyles } />
|
||||
@@ -186,7 +207,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
/**
|
||||
* Renders the time at which the message was sent, if necessary.
|
||||
*
|
||||
* @returns {React$Element<*> | null}
|
||||
* @returns {React.ReactElement<*> | null}
|
||||
*/
|
||||
_renderTimestamp() {
|
||||
if (!this.props.showTimestamp) {
|
||||
@@ -205,6 +226,7 @@ class ChatMessage extends Component<IChatMessageProps> {
|
||||
* Maps part of the redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IChatMessageProps} message - Message object.
|
||||
* @returns {IProps}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, { message }: IChatMessageProps) {
|
||||
|
||||
@@ -197,7 +197,7 @@ const ChatMessage = ({
|
||||
className = { classes.replyButtonContainer }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
participantID = { message.id } />
|
||||
participantID = { message.participantId } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -66,7 +66,7 @@ const ChatMessageGroup = ({ className = '', messages }: IProps) => {
|
||||
<div className = { clsx(classes.groupContainer, className) }>
|
||||
<Avatar
|
||||
className = { clsx(classes.avatar, 'avatar') }
|
||||
participantId = { messages[0].id }
|
||||
participantId = { messages[0].participantId }
|
||||
size = { 32 } />
|
||||
<div className = { `${classes.messageGroup} chat-message-group ${className}` }>
|
||||
{messages.map((message, i) => (
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* Maximum number of characters allowed.
|
||||
*/
|
||||
export const CHAR_LIMIT = 500;
|
||||
|
||||
/**
|
||||
* The size of the chat. Equal to $sidebarWidth SCSS variable.
|
||||
*/
|
||||
|
||||
@@ -172,7 +172,7 @@ export function getMessageText(message: IMessage) {
|
||||
*/
|
||||
export function getCanReplyToMessage(state: IReduxState, message: IMessage) {
|
||||
const { knocking } = state['features/lobby'];
|
||||
const participant = getParticipantById(state, message.id);
|
||||
const participant = getParticipantById(state, message.participantId);
|
||||
|
||||
return Boolean(participant)
|
||||
&& (message.privateMessage || (message.lobbyChat && !knocking))
|
||||
|
||||
@@ -125,7 +125,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
store.dispatch(pushReactions(data.reactions));
|
||||
|
||||
_handleReceivedMessage(store, {
|
||||
id: participant.getId(),
|
||||
participantId: participant.getId(),
|
||||
message: getReactionMessageFromBuffer(data.reactions),
|
||||
privateMessage: false,
|
||||
lobbyChat: false,
|
||||
@@ -137,12 +137,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
case NON_PARTICIPANT_MESSAGE_RECEIVED: {
|
||||
const { id, json: data } = action;
|
||||
const { participantId, json: data } = action;
|
||||
|
||||
if (data?.type === MESSAGE_TYPE_SYSTEM && data.message) {
|
||||
_handleReceivedMessage(store, {
|
||||
displayName: data.displayName ?? i18next.t('chat.systemDisplayName'),
|
||||
id,
|
||||
participantId,
|
||||
lobbyChat: false,
|
||||
message: data.message,
|
||||
privateMessage: true,
|
||||
@@ -213,7 +213,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case ADD_REACTION_MESSAGE: {
|
||||
if (localParticipant?.id) {
|
||||
_handleReceivedMessage(store, {
|
||||
id: localParticipant.id,
|
||||
participantId: localParticipant.id,
|
||||
message: action.message,
|
||||
privateMessage: false,
|
||||
timestamp: Date.now(),
|
||||
@@ -274,25 +274,30 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.MESSAGE_RECEIVED,
|
||||
// eslint-disable-next-line max-params
|
||||
(id: string, message: string, timestamp: number, displayName: string, isGuest?: boolean) => {
|
||||
/* eslint-disable max-params */
|
||||
(participantId: string, message: string, timestamp: number,
|
||||
displayName: string, isGuest: boolean, messageId: string) => {
|
||||
/* eslint-enable max-params */
|
||||
_onConferenceMessageReceived(store, {
|
||||
id: id || displayName, // in case of messages coming from visitors we can have unknown id
|
||||
// in case of messages coming from visitors we can have unknown id
|
||||
participantId: participantId || displayName,
|
||||
message,
|
||||
timestamp,
|
||||
displayName,
|
||||
isGuest,
|
||||
messageId,
|
||||
privateMessage: false });
|
||||
}
|
||||
);
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.PRIVATE_MESSAGE_RECEIVED,
|
||||
(id: string, message: string, timestamp: number) => {
|
||||
(participantId: string, message: string, timestamp: number, messageId: string) => {
|
||||
_onConferenceMessageReceived(store, {
|
||||
id,
|
||||
participantId,
|
||||
message,
|
||||
timestamp,
|
||||
messageId,
|
||||
privateMessage: true
|
||||
});
|
||||
}
|
||||
@@ -311,25 +316,29 @@ function _addChatMsgListener(conference: IJitsiConference, store: IStore) {
|
||||
* @param {Object} message - The message object.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConferenceMessageReceived(store: IStore, { displayName, id, isGuest, message, timestamp, privateMessage }: {
|
||||
displayName?: string; id: string; isGuest?: boolean;
|
||||
message: string; privateMessage: boolean; timestamp: number; }) {
|
||||
function _onConferenceMessageReceived(store: IStore,
|
||||
{ displayName, isGuest, message, messageId, participantId, privateMessage, timestamp }: {
|
||||
displayName?: string; isGuest?: boolean; message: string; messageId?: string;
|
||||
participantId: string; privateMessage: boolean; timestamp: number; }
|
||||
) {
|
||||
|
||||
const isGif = isGifMessage(message);
|
||||
|
||||
if (isGif) {
|
||||
_handleGifMessageReceived(store, id, message);
|
||||
_handleGifMessageReceived(store, participantId, message);
|
||||
if (getGifDisplayMode(store.getState()) === 'tile') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_handleReceivedMessage(store, {
|
||||
displayName,
|
||||
id,
|
||||
isGuest,
|
||||
participantId,
|
||||
message,
|
||||
privateMessage,
|
||||
lobbyChat: false,
|
||||
timestamp
|
||||
timestamp,
|
||||
messageId
|
||||
}, true, isGif);
|
||||
}
|
||||
|
||||
@@ -337,14 +346,14 @@ function _onConferenceMessageReceived(store: IStore, { displayName, id, isGuest,
|
||||
* Handles a received gif message.
|
||||
*
|
||||
* @param {Object} store - Redux store.
|
||||
* @param {string} id - Id of the participant that sent the message.
|
||||
* @param {string} participantId - Id of the participant that sent the message.
|
||||
* @param {string} message - The message sent.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleGifMessageReceived(store: IStore, id: string, message: string) {
|
||||
function _handleGifMessageReceived(store: IStore, participantId: string, message: string) {
|
||||
const url = message.substring(GIF_PREFIX.length, message.length - 1);
|
||||
|
||||
store.dispatch(addGif(id, url));
|
||||
store.dispatch(addGif(participantId, url));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +383,7 @@ function _handleChatError({ dispatch }: IStore, error: Error) {
|
||||
export function handleLobbyMessageReceived(message: string, participantId: string) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
_handleReceivedMessage({ dispatch,
|
||||
getState }, { id: participantId,
|
||||
getState }, { participantId,
|
||||
message,
|
||||
privateMessage: false,
|
||||
lobbyChat: true,
|
||||
@@ -387,18 +396,18 @@ export function handleLobbyMessageReceived(message: string, participantId: strin
|
||||
* Function to get lobby chat user display name.
|
||||
*
|
||||
* @param {Store} state - The Redux store.
|
||||
* @param {string} id - The knocking participant id.
|
||||
* @param {string} participantId - The knocking participant id.
|
||||
* @returns {string}
|
||||
*/
|
||||
function getLobbyChatDisplayName(state: IReduxState, id: string) {
|
||||
function getLobbyChatDisplayName(state: IReduxState, participantId: string) {
|
||||
const { knockingParticipants } = state['features/lobby'];
|
||||
const { lobbyMessageRecipient } = state['features/chat'];
|
||||
|
||||
if (id === lobbyMessageRecipient?.id) {
|
||||
if (participantId === lobbyMessageRecipient?.id) {
|
||||
return lobbyMessageRecipient.name;
|
||||
}
|
||||
|
||||
const knockingParticipant = knockingParticipants.find(p => p.id === id);
|
||||
const knockingParticipant = knockingParticipants.find(p => p.id === participantId);
|
||||
|
||||
if (knockingParticipant) {
|
||||
return knockingParticipant.name;
|
||||
@@ -417,9 +426,9 @@ function getLobbyChatDisplayName(state: IReduxState, id: string) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
{ displayName, id, isGuest, message, privateMessage, timestamp, lobbyChat }: {
|
||||
displayName?: string; id: string; isGuest?: boolean; lobbyChat: boolean;
|
||||
message: string; privateMessage: boolean; timestamp: number; },
|
||||
{ displayName, isGuest, lobbyChat, message, messageId, participantId, privateMessage, timestamp }: {
|
||||
displayName?: string; isGuest?: boolean; lobbyChat: boolean; message: string;
|
||||
messageId?: string; participantId: string; privateMessage: boolean; timestamp: number; },
|
||||
shouldPlaySound = true,
|
||||
isReaction = false
|
||||
) {
|
||||
@@ -434,12 +443,12 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
|
||||
// Provide a default for the case when a message is being
|
||||
// backfilled for a participant that has left the conference.
|
||||
const participant = getParticipantById(state, id) || { local: undefined };
|
||||
const participant = getParticipantById(state, participantId) || { local: undefined };
|
||||
|
||||
const localParticipant = getLocalParticipant(getState);
|
||||
let displayNameToShow = lobbyChat
|
||||
? getLobbyChatDisplayName(state, id)
|
||||
: displayName || getParticipantDisplayName(state, id);
|
||||
? getLobbyChatDisplayName(state, participantId)
|
||||
: displayName || getParticipantDisplayName(state, participantId);
|
||||
const hasRead = participant.local || isChatOpen;
|
||||
const timestampToDate = timestamp ? new Date(timestamp) : new Date();
|
||||
const millisecondsTimestamp = timestampToDate.getTime();
|
||||
@@ -455,13 +464,14 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
dispatch(addMessage({
|
||||
displayName: displayNameToShow,
|
||||
hasRead,
|
||||
id,
|
||||
participantId,
|
||||
messageType: participant.local ? MESSAGE_TYPE_LOCAL : MESSAGE_TYPE_REMOTE,
|
||||
message,
|
||||
privateMessage,
|
||||
lobbyChat,
|
||||
recipient: getParticipantDisplayName(state, localParticipant?.id ?? ''),
|
||||
timestamp: millisecondsTimestamp,
|
||||
messageId,
|
||||
isReaction
|
||||
}));
|
||||
|
||||
@@ -477,7 +487,7 @@ function _handleReceivedMessage({ dispatch, getState }: IStore,
|
||||
|
||||
APP.API.notifyReceivedChatMessage({
|
||||
body: message,
|
||||
id,
|
||||
from: participantId,
|
||||
nick: displayNameToShow,
|
||||
privateMessage,
|
||||
ts: timestamp
|
||||
@@ -512,7 +522,7 @@ function _persistSentPrivateMessage({ dispatch, getState }: IStore, recipientID:
|
||||
dispatch(addMessage({
|
||||
displayName,
|
||||
hasRead: true,
|
||||
id: localParticipant.id,
|
||||
participantId: localParticipant.id,
|
||||
messageType: MESSAGE_TYPE_LOCAL,
|
||||
message,
|
||||
privateMessage: !isLobbyPrivateMessage,
|
||||
@@ -562,7 +572,7 @@ function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
|
||||
|
||||
if (lastMessage.privateMessage) {
|
||||
// We show the notice if the last received message was private.
|
||||
return lastMessage.id;
|
||||
return lastMessage.participantId;
|
||||
}
|
||||
|
||||
// But messages may come rapidly, we want to protect our users from mis-sending a message
|
||||
@@ -577,7 +587,7 @@ function _shouldSendPrivateMessageTo(state: IReduxState, action: AnyAction) {
|
||||
? recentPrivateMessages[0] : recentPrivateMessages[recentPrivateMessages.length - 1];
|
||||
|
||||
if (recentPrivateMessage) {
|
||||
return recentPrivateMessage.id;
|
||||
return recentPrivateMessage.participantId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { ILocalParticipant, IParticipant } from '../base/participants/types';
|
||||
import ReducerRegistry from '../base/redux/ReducerRegistry';
|
||||
|
||||
@@ -48,9 +46,9 @@ ReducerRegistry.register<IChatState>('features/chat', (state = DEFAULT_STATE, ac
|
||||
const newMessage: IMessage = {
|
||||
displayName: action.displayName,
|
||||
error: action.error,
|
||||
id: action.id,
|
||||
participantId: action.participantId,
|
||||
isReaction: action.isReaction,
|
||||
messageId: uuidv4(),
|
||||
messageId: action.messageId,
|
||||
messageType: action.messageType,
|
||||
message: action.message,
|
||||
privateMessage: action.privateMessage,
|
||||
|
||||
@@ -5,12 +5,12 @@ import { IStore } from '../app/types';
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
id: string;
|
||||
isReaction: boolean;
|
||||
lobbyChat: boolean;
|
||||
message: string;
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
participantId: string;
|
||||
privateMessage: boolean;
|
||||
recipient: string;
|
||||
timestamp: number;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ENDPOINT_MESSAGE_RECEIVED
|
||||
} from '../base/conference/actionTypes';
|
||||
import { getCurrentConference } from '../base/conference/functions';
|
||||
import { getDisableLowerHandByModerator } from '../base/config/functions.any';
|
||||
import { getURLWithoutParamsNormalized } from '../base/connection/utils';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
import { isDialogOpen } from '../base/dialog/functions';
|
||||
@@ -75,9 +76,11 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
case ENDPOINT_MESSAGE_RECEIVED: {
|
||||
const { participant, data } = action;
|
||||
const { dispatch } = store;
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
if (data.name === LOWER_HAND_MESSAGE && participant.isModerator()) {
|
||||
if (data.name === LOWER_HAND_MESSAGE
|
||||
&& participant.isModerator()
|
||||
&& !getDisableLowerHandByModerator(getState())) {
|
||||
dispatch(raiseHand(false));
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
import {
|
||||
getDominantSpeakerParticipant,
|
||||
getLocalParticipant,
|
||||
getParticipantById
|
||||
getParticipantById,
|
||||
getParticipantDisplayName
|
||||
} from '../base/participants/functions';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { getBaseUrl } from '../base/util/helpers';
|
||||
@@ -133,15 +134,29 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.API.notifyDataChannelOpened();
|
||||
break;
|
||||
|
||||
case KICKED_OUT:
|
||||
case KICKED_OUT: {
|
||||
const state = store.getState();
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
if (!localParticipant) {
|
||||
break;
|
||||
}
|
||||
|
||||
const pId = action.participant.getId();
|
||||
|
||||
APP.API.notifyKickedOut(
|
||||
{
|
||||
id: getLocalParticipant(store.getState())?.id,
|
||||
id: localParticipant.id,
|
||||
name: getParticipantDisplayName(state, localParticipant.id),
|
||||
local: true
|
||||
},
|
||||
{ id: action.participant ? action.participant.getId() : undefined }
|
||||
{
|
||||
id: pId,
|
||||
name: getParticipantDisplayName(state, pId)
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case NOTIFY_CAMERA_ERROR:
|
||||
if (action.error) {
|
||||
@@ -156,14 +171,28 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
break;
|
||||
|
||||
case PARTICIPANT_KICKED:
|
||||
case PARTICIPANT_KICKED: {
|
||||
const state = store.getState();
|
||||
const kickedParticipant = getParticipantById(state, action.kicked);
|
||||
const kickerParticipant = getParticipantById(state, action.kicker);
|
||||
|
||||
if (!kickerParticipant || !kickedParticipant) {
|
||||
break;
|
||||
}
|
||||
|
||||
APP.API.notifyKickedOut(
|
||||
{
|
||||
id: action.kicked,
|
||||
local: false
|
||||
id: kickedParticipant.id,
|
||||
local: kickedParticipant.local,
|
||||
name: getParticipantDisplayName(state, kickedParticipant.id)
|
||||
},
|
||||
{ id: action.kicker });
|
||||
{
|
||||
id: kickerParticipant.id,
|
||||
local: kickerParticipant.local,
|
||||
name: getParticipantDisplayName(state, kickerParticipant.id)
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case PARTICIPANT_LEFT: {
|
||||
const { participant } = action;
|
||||
|
||||
@@ -82,14 +82,19 @@ export function invite(
|
||||
const { conference, password } = state['features/base/conference'];
|
||||
|
||||
if (typeof conference === 'undefined') {
|
||||
// Only keep invitees which can get an invite request from Jitsi UI
|
||||
const jitsiInvitees = invitees.filter(({ type }) => type !== INVITE_TYPES.EMAIL);
|
||||
|
||||
// Invite will fail before CONFERENCE_JOIN. The request will be
|
||||
// cached in order to be executed on CONFERENCE_JOIN.
|
||||
return new Promise(resolve => {
|
||||
dispatch(addPendingInviteRequest({
|
||||
invitees,
|
||||
callback: (failedInvitees: any) => resolve(failedInvitees)
|
||||
}));
|
||||
});
|
||||
if (jitsiInvitees.length) {
|
||||
return new Promise(resolve => {
|
||||
dispatch(addPendingInviteRequest({
|
||||
invitees: jitsiInvitees,
|
||||
callback: (failedInvitees: any) => resolve(failedInvitees)
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let allInvitePromises: Promise<any>[] = [];
|
||||
@@ -112,10 +117,12 @@ export function invite(
|
||||
|
||||
// For each number, dial out. On success, remove the number from
|
||||
// {@link invitesLeftToSend}.
|
||||
const phoneInvitePromises = phoneNumbers.map(item => {
|
||||
const numberToInvite = item.number;
|
||||
const phoneInvitePromises = typeof conference === 'undefined'
|
||||
? []
|
||||
: phoneNumbers.map(item => {
|
||||
const numberToInvite = item.number;
|
||||
|
||||
return conference.dial(numberToInvite)
|
||||
return conference.dial(numberToInvite)
|
||||
.then(() => {
|
||||
invitesLeftToSend
|
||||
= invitesLeftToSend.filter(
|
||||
@@ -123,13 +130,13 @@ export function invite(
|
||||
})
|
||||
.catch((error: Error) =>
|
||||
logger.error('Error inviting phone number:', error));
|
||||
});
|
||||
});
|
||||
|
||||
allInvitePromises = allInvitePromises.concat(phoneInvitePromises);
|
||||
|
||||
const usersAndRooms
|
||||
= invitesLeftToSend.filter(
|
||||
({ type }) => [ INVITE_TYPES.USER, INVITE_TYPES.ROOM ].includes(type));
|
||||
({ type }) => [ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.ROOM ].includes(type));
|
||||
|
||||
if (usersAndRooms.length) {
|
||||
// Send a request to invite all the rooms and users. On success,
|
||||
@@ -139,12 +146,12 @@ export function invite(
|
||||
(callFlowsEnabled
|
||||
? inviteServiceCallFlowsUrl : inviteServiceUrl) ?? '',
|
||||
inviteUrl,
|
||||
jwt,
|
||||
usersAndRooms)
|
||||
usersAndRooms,
|
||||
state)
|
||||
.then(() => {
|
||||
invitesLeftToSend
|
||||
= invitesLeftToSend.filter(
|
||||
({ type }) => ![ INVITE_TYPES.USER, INVITE_TYPES.ROOM ].includes(type));
|
||||
({ type }) => ![ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.ROOM ].includes(type));
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch(setCalleeInfoVisible(false));
|
||||
|
||||
@@ -61,6 +61,11 @@ export interface IProps {
|
||||
*/
|
||||
_peopleSearchQueryTypes: Array<string>;
|
||||
|
||||
/**
|
||||
* The localStorage key holding the alternative token for people directory.
|
||||
*/
|
||||
_peopleSearchTokenLocation: string;
|
||||
|
||||
/**
|
||||
* The URL pointing to the service allowing for people search.
|
||||
*/
|
||||
@@ -254,6 +259,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
||||
_jwt: jwt,
|
||||
_peopleSearchQueryTypes: peopleSearchQueryTypes,
|
||||
_peopleSearchUrl: peopleSearchUrl,
|
||||
_peopleSearchTokenLocation: peopleSearchTokenLocation,
|
||||
_region: region,
|
||||
_sipInviteEnabled: sipInviteEnabled
|
||||
} = this.props;
|
||||
@@ -266,6 +272,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
|
||||
jwt,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl,
|
||||
peopleSearchTokenLocation,
|
||||
region,
|
||||
sipInviteEnabled
|
||||
};
|
||||
@@ -295,7 +302,8 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
dialOutAuthUrl,
|
||||
dialOutRegionUrl,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl
|
||||
peopleSearchUrl,
|
||||
peopleSearchTokenLocation
|
||||
} = state['features/base/config'];
|
||||
|
||||
return {
|
||||
@@ -308,6 +316,7 @@ export function _mapStateToProps(state: IReduxState) {
|
||||
_jwt: state['features/base/jwt'].jwt ?? '',
|
||||
_peopleSearchQueryTypes: peopleSearchQueryTypes ?? [],
|
||||
_peopleSearchUrl: peopleSearchUrl ?? '',
|
||||
_peopleSearchTokenLocation: peopleSearchTokenLocation ?? '',
|
||||
_region: getMeetingRegion(state),
|
||||
_sipInviteEnabled: isSipInviteEnabled(state)
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ import Icon from '../../../../base/icons/components/Icon';
|
||||
import {
|
||||
IconCheck,
|
||||
IconCloseCircle,
|
||||
IconEnvelope,
|
||||
IconPhoneRinging,
|
||||
IconSearch,
|
||||
IconShare
|
||||
@@ -260,6 +261,12 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
key: item.id || item.user_id,
|
||||
title: item.name
|
||||
};
|
||||
case INVITE_TYPES.EMAIL:
|
||||
return {
|
||||
avatar: item.avatar || IconEnvelope,
|
||||
key: item.id || item.user_id,
|
||||
title: item.name
|
||||
};
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -273,7 +280,11 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* @returns {string}
|
||||
*/
|
||||
_keyExtractor(item: any) {
|
||||
return item.type === INVITE_TYPES.USER ? item.id || item.user_id : item.number;
|
||||
if (item.type === INVITE_TYPES.USER || item.type === INVITE_TYPES.EMAIL) {
|
||||
return item.id || item.user_id;
|
||||
}
|
||||
|
||||
return item.number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -451,6 +462,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
selected = inviteItems.find(_.matchesProperty('number', item.number));
|
||||
break;
|
||||
case INVITE_TYPES.USER:
|
||||
case INVITE_TYPES.EMAIL:
|
||||
selected = item.id
|
||||
? inviteItems.find(_.matchesProperty('id', item.id))
|
||||
: inviteItems.find(_.matchesProperty('user_id', item.user_id));
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IReduxState, IStore } from '../../../../app/types';
|
||||
import Avatar from '../../../../base/avatar/components/Avatar';
|
||||
import { translate } from '../../../../base/i18n/functions';
|
||||
import Icon from '../../../../base/icons/components/Icon';
|
||||
import { IconPhoneRinging } from '../../../../base/icons/svg';
|
||||
import { IconEnvelope, IconPhoneRinging, IconUser } from '../../../../base/icons/svg';
|
||||
import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
|
||||
import Button from '../../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../../base/ui/constants.any';
|
||||
@@ -302,9 +302,12 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_getAvatar(user: any, className = 'avatar-small') {
|
||||
const defaultIcon = user.type === INVITE_TYPES.EMAIL ? IconEnvelope : IconUser;
|
||||
|
||||
return (
|
||||
<Avatar
|
||||
className = { className }
|
||||
defaultIcon = { defaultIcon }
|
||||
size = { 32 }
|
||||
status = { user.status }
|
||||
url = { user.avatar } />
|
||||
@@ -325,7 +328,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
|
||||
_parseQueryResults(response: IInvitee[] = []) {
|
||||
const { t, _dialOutEnabled } = this.props;
|
||||
|
||||
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
|
||||
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
|
||||
const users = response.filter(item => userTypes.includes(item.type));
|
||||
const userDisplayItems: any = [];
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ export const SIP_ADDRESS_REGEX = /^[+a-zA-Z0-9]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)
|
||||
* Different invite types mapping.
|
||||
*/
|
||||
export const INVITE_TYPES = {
|
||||
EMAIL: 'email',
|
||||
PHONE: 'phone',
|
||||
ROOM: 'room',
|
||||
SIP: 'sip',
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { IStateful } from '../base/app/types';
|
||||
import { getRoomName } from '../base/conference/functions';
|
||||
@@ -143,6 +146,11 @@ export type GetInviteResultsOptions = {
|
||||
*/
|
||||
peopleSearchQueryTypes: Array<string>;
|
||||
|
||||
/**
|
||||
* Key in localStorage holding the alternative token for people directory.
|
||||
*/
|
||||
peopleSearchTokenLocation?: string;
|
||||
|
||||
/**
|
||||
* The url to query for people.
|
||||
*/
|
||||
@@ -181,6 +189,7 @@ export function getInviteResultsForQuery(
|
||||
dialOutEnabled,
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchUrl,
|
||||
peopleSearchTokenLocation,
|
||||
region,
|
||||
sipInviteEnabled,
|
||||
jwt
|
||||
@@ -193,7 +202,8 @@ export function getInviteResultsForQuery(
|
||||
peopleSearchUrl,
|
||||
jwt,
|
||||
text,
|
||||
peopleSearchQueryTypes);
|
||||
peopleSearchQueryTypes,
|
||||
peopleSearchTokenLocation);
|
||||
} else {
|
||||
peopleSearchPromise = Promise.resolve([]);
|
||||
}
|
||||
@@ -411,29 +421,40 @@ export function getInviteTypeCounts(inviteItems: IInvitee[] = []) {
|
||||
* @param {string} inviteServiceUrl - The invite service that generates the
|
||||
* invitation.
|
||||
* @param {string} inviteUrl - The url to the conference.
|
||||
* @param {string} jwt - The jwt token to pass to the search service.
|
||||
* @param {Immutable.List} inviteItems - The list of the "user" or "room" type
|
||||
* items to invite.
|
||||
* @param {IReduxState} state - Global state.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function invitePeopleAndChatRooms(
|
||||
inviteServiceUrl: string,
|
||||
inviteUrl: string,
|
||||
jwt: string,
|
||||
inviteItems: Array<Object>
|
||||
inviteItems: Array<Object>,
|
||||
state: IReduxState
|
||||
): Promise<any> {
|
||||
|
||||
if (!inviteItems || inviteItems.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Parse all the query strings of the search directory endpoint
|
||||
const { jwt = '' } = state['features/base/jwt'];
|
||||
const { peopleSearchTokenLocation } = state['features/base/config'];
|
||||
|
||||
let token = jwt;
|
||||
|
||||
// If token is empty, check for alternate token
|
||||
if (!token && peopleSearchTokenLocation) {
|
||||
token = jitsiLocalStorage.getItem(peopleSearchTokenLocation) ?? '';
|
||||
}
|
||||
|
||||
const headers = {
|
||||
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {},
|
||||
...token ? { 'Authorization': `Bearer ${token}` } : {},
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
return fetch(
|
||||
`${inviteServiceUrl}`,
|
||||
inviteServiceUrl,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
'invited': inviteItems,
|
||||
@@ -452,9 +473,14 @@ export function invitePeopleAndChatRooms(
|
||||
* @returns {boolean} Indication of whether adding people is currently enabled.
|
||||
*/
|
||||
export function isAddPeopleEnabled(state: IReduxState): boolean {
|
||||
const { peopleSearchUrl } = state['features/base/config'];
|
||||
const {
|
||||
peopleSearchUrl,
|
||||
peopleSearchTokenLocation
|
||||
} = state['features/base/config'];
|
||||
|
||||
return Boolean(state['features/base/jwt'].jwt && Boolean(peopleSearchUrl) && !isVpaasMeeting(state));
|
||||
const hasToken = Boolean(state['features/base/jwt'].jwt || Boolean(peopleSearchTokenLocation));
|
||||
|
||||
return Boolean(hasToken && Boolean(peopleSearchUrl) && !isVpaasMeeting(state));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -534,21 +560,30 @@ function isPhoneNumberRegex(): RegExp {
|
||||
* @param {string} jwt - The jwt token to pass to the search service.
|
||||
* @param {string} text - Text to search.
|
||||
* @param {Array<string>} queryTypes - Array with the query types that will be
|
||||
* executed - "conferenceRooms" | "user" | "room".
|
||||
* executed - "conferenceRooms" | "user" | "room" | "email".
|
||||
* @param {string} peopleSearchTokenLocation - The localStorage key holding the token value for alternate auth.
|
||||
* @returns {Promise} - The promise created by the request.
|
||||
*/
|
||||
export function searchDirectory( // eslint-disable-line max-params
|
||||
serviceUrl: string,
|
||||
jwt: string,
|
||||
text: string,
|
||||
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room' ]
|
||||
queryTypes: Array<string> = [ 'conferenceRooms', 'user', 'room', 'email' ],
|
||||
peopleSearchTokenLocation?: string
|
||||
): Promise<Array<{ type: string; }>> {
|
||||
|
||||
const query = encodeURIComponent(text);
|
||||
const queryTypesString = encodeURIComponent(JSON.stringify(queryTypes));
|
||||
|
||||
let token = jwt;
|
||||
|
||||
// If token is empty, check for alternate token
|
||||
if (!token && peopleSearchTokenLocation) {
|
||||
token = jitsiLocalStorage.getItem(peopleSearchTokenLocation) ?? '';
|
||||
}
|
||||
|
||||
const headers = {
|
||||
...jwt ? { 'Authorization': `Bearer ${jwt}` } : {}
|
||||
...token ? { 'Authorization': `Bearer ${token}` } : {}
|
||||
};
|
||||
|
||||
return fetch(`${serviceUrl}?query=${query}&queryTypes=${
|
||||
|
||||
@@ -18,14 +18,3 @@ export const READY_TO_CLOSE = 'READY_TO_CLOSE';
|
||||
*/
|
||||
export const SCREEN_SHARE_PARTICIPANTS_UPDATED
|
||||
= 'SCREEN_SHARE_PARTICIPANTS_UPDATED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a custom button from the overflow menu was pressed.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
* id: string,
|
||||
* text: string
|
||||
* }}
|
||||
*/
|
||||
export const CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED = 'CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED';
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
READY_TO_CLOSE,
|
||||
SCREEN_SHARE_PARTICIPANTS_UPDATED
|
||||
} from './actionTypes';
|
||||
import { READY_TO_CLOSE, SCREEN_SHARE_PARTICIPANTS_UPDATED } from './actionTypes';
|
||||
|
||||
|
||||
/**
|
||||
@@ -35,22 +31,3 @@ export function setParticipantsWithScreenShare(participantIds: Array<string>) {
|
||||
participantIds
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (redux) action which that a custom overflow menu button was pressed.
|
||||
*
|
||||
* @param {string} id - The id for the custom button.
|
||||
* @param {string} text - The label for the custom button.
|
||||
* @returns {{
|
||||
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
* id: string,
|
||||
* text: string
|
||||
* }}
|
||||
*/
|
||||
export function customOverflowMenuButtonPressed(id: string, text: string) {
|
||||
return {
|
||||
type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
id,
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,15 +52,13 @@ import { ITrack } from '../../base/tracks/types';
|
||||
import { CLOSE_CHAT, OPEN_CHAT } from '../../chat/actionTypes';
|
||||
import { closeChat, openChat, sendMessage, setPrivateMessageRecipient } from '../../chat/actions.native';
|
||||
import { setRequestingSubtitles } from '../../subtitles/actions.any';
|
||||
import { CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED } from '../../toolbox/actionTypes';
|
||||
import { muteLocal } from '../../video-menu/actions.native';
|
||||
import { ENTER_PICTURE_IN_PICTURE } from '../picture-in-picture/actionTypes';
|
||||
// @ts-ignore
|
||||
import { isExternalAPIAvailable } from '../react-native-sdk/functions';
|
||||
|
||||
import {
|
||||
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
READY_TO_CLOSE
|
||||
} from './actionTypes';
|
||||
import { READY_TO_CLOSE } from './actionTypes';
|
||||
import { setParticipantsWithScreenShare } from './actions';
|
||||
import { participantToParticipantInfo, sendEvent } from './functions';
|
||||
import logger from './logger';
|
||||
@@ -82,12 +80,6 @@ const CHAT_TOGGLED = 'CHAT_TOGGLED';
|
||||
*/
|
||||
const CONFERENCE_TERMINATED = 'CONFERENCE_TERMINATED';
|
||||
|
||||
/**
|
||||
* Event which will be emitted on the native side to indicate that the custom overflow menu button was pressed.
|
||||
*/
|
||||
const CUSTOM_MENU_BUTTON_PRESSED = 'CUSTOM_MENU_BUTTON_PRESSED';
|
||||
|
||||
|
||||
/**
|
||||
* Event which will be emitted on the native side to indicate a message was received
|
||||
* through the channel.
|
||||
@@ -199,7 +191,7 @@ externalAPIEnabled && MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
sendEvent(
|
||||
store,
|
||||
CUSTOM_MENU_BUTTON_PRESSED,
|
||||
CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
{
|
||||
id,
|
||||
text
|
||||
|
||||
@@ -14,6 +14,7 @@ import BaseTheme from '../../../base/ui/components/BaseTheme.native';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import IconButton from '../../../base/ui/components/native/IconButton';
|
||||
import { BUTTON_MODES, BUTTON_TYPES } from '../../../base/ui/constants.native';
|
||||
import { CHAR_LIMIT } from '../../../chat/constants';
|
||||
import { replaceNonUnicodeEmojis } from '../../../chat/functions';
|
||||
import { NOTIFICATION_ICON, NOTIFICATION_TYPE } from '../../constants';
|
||||
import { INotificationProps } from '../../types';
|
||||
@@ -153,6 +154,7 @@ const Notification = ({
|
||||
return (
|
||||
<>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.contentTextTitle as TextStyle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
@@ -160,8 +162,9 @@ const Notification = ({
|
||||
descriptionArray.map((line, index) => (
|
||||
<Text
|
||||
key = { index }
|
||||
numberOfLines = { 3 }
|
||||
style = { styles.contentText }>
|
||||
{ replaceNonUnicodeEmojis(line) }
|
||||
{ line.length >= CHAR_LIMIT ? line : replaceNonUnicodeEmojis(line) }
|
||||
</Text>
|
||||
))
|
||||
}
|
||||
@@ -170,7 +173,9 @@ const Notification = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style = { styles.contentTextTitle as TextStyle }>
|
||||
<Text
|
||||
numberOfLines = { 1 }
|
||||
style = { styles.contentTextTitle as TextStyle }>
|
||||
{ titleText }
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -13,7 +13,6 @@ const notification = {
|
||||
borderLeftWidth: BaseTheme.spacing[1],
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
height: 'auto',
|
||||
marginVertical: BaseTheme.spacing[1],
|
||||
maxWidth: 416,
|
||||
width: '100%'
|
||||
|
||||
@@ -7,6 +7,17 @@
|
||||
*/
|
||||
export const CLEAR_TOOLBOX_TIMEOUT = 'CLEAR_TOOLBOX_TIMEOUT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a custom button from the overflow menu was pressed.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
* id: string,
|
||||
* text: string
|
||||
* }}
|
||||
*/
|
||||
export const CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED = 'CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which updates whether the conference is or is not
|
||||
* currently in full screen view.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED } from './actionTypes';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
@@ -22,3 +24,22 @@ export function showToolbox(_timeout?: number): any {
|
||||
export function setOverflowMenuVisible(_visible: boolean): any {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a (redux) action which that a custom overflow menu button was pressed.
|
||||
*
|
||||
* @param {string} id - The id for the custom button.
|
||||
* @param {string} text - The label for the custom button.
|
||||
* @returns {{
|
||||
* type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
* id: string,
|
||||
* text: string
|
||||
* }}
|
||||
*/
|
||||
export function customOverflowMenuButtonPressed(id: string, text: string) {
|
||||
return {
|
||||
type: CUSTOM_OVERFLOW_MENU_BUTTON_PRESSED,
|
||||
id,
|
||||
text
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import SettingsButton from '../../../base/settings/components/native/SettingsBut
|
||||
import BreakoutRoomsButton
|
||||
from '../../../breakout-rooms/components/native/BreakoutRoomsButton';
|
||||
import SharedDocumentButton from '../../../etherpad/components/SharedDocumentButton.native';
|
||||
import { customOverflowMenuButtonPressed } from '../../../mobile/external-api/actions';
|
||||
import ReactionMenu from '../../../reactions/components/native/ReactionMenu';
|
||||
import { shouldDisplayReactionsButtons } from '../../../reactions/functions.any';
|
||||
import LiveStreamButton from '../../../recording/components/LiveStream/native/LiveStreamButton';
|
||||
@@ -25,6 +24,7 @@ import ClosedCaptionButton from '../../../subtitles/components/native/ClosedCapt
|
||||
import TileViewButton from '../../../video-layout/components/TileViewButton';
|
||||
import styles from '../../../video-menu/components/native/styles';
|
||||
import WhiteboardButton from '../../../whiteboard/components/native/WhiteboardButton';
|
||||
import { customOverflowMenuButtonPressed } from '../../actions.native';
|
||||
import { getMovableButtons } from '../../functions.native';
|
||||
|
||||
import AudioOnlyButton from './AudioOnlyButton';
|
||||
|
||||
@@ -9,10 +9,10 @@ import { IconArrowUp } from '../../../base/icons/svg';
|
||||
import JitsiMeetJS from '../../../base/lib-jitsi-meet/_';
|
||||
import { IGUMPendingState } from '../../../base/media/types';
|
||||
import ToolboxButtonWithIcon from '../../../base/toolbox/components/web/ToolboxButtonWithIcon';
|
||||
import { toggleAudioSettings } from '../../../settings/actions';
|
||||
import { toggleAudioSettings } from '../../../settings/actions.web';
|
||||
import AudioSettingsPopup from '../../../settings/components/web/audio/AudioSettingsPopup';
|
||||
import { getAudioSettingsVisibility } from '../../../settings/functions';
|
||||
import { isAudioSettingsButtonDisabled } from '../../functions';
|
||||
import { getAudioSettingsVisibility } from '../../../settings/functions.web';
|
||||
import { isAudioSettingsButtonDisabled } from '../../functions.web';
|
||||
|
||||
import AudioMuteButton from './AudioMuteButton';
|
||||
|
||||
|
||||
@@ -10,17 +10,17 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too
|
||||
import { startScreenShareFlow } from '../../../screen-share/actions.web';
|
||||
import { isScreenVideoShared } from '../../../screen-share/functions';
|
||||
import { closeOverflowMenuIfOpen } from '../../actions.web';
|
||||
import { isDesktopShareButtonDisabled } from '../../functions';
|
||||
import { isDesktopShareButtonDisabled } from '../../functions.web';
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
|
||||
/**
|
||||
* Whether or not screensharing is initialized.
|
||||
* Whether or not screen-sharing is initialized.
|
||||
*/
|
||||
_desktopSharingEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the local participant is screensharing.
|
||||
* Whether or not the local participant is screen-sharing.
|
||||
*/
|
||||
_screensharing: boolean;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class ShareDesktopButton extends AbstractButton<IProps> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
// Disable the screenshare button if the video sender limit is reached and there is no video or media share in
|
||||
// Disable the screen-share button if the video sender limit is reached and there is no video or media share in
|
||||
// progress.
|
||||
const desktopSharingEnabled
|
||||
= JitsiMeetJS.isDesktopSharingEnabled() && !isDesktopShareButtonDisabled(state);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useFeedbackButton } from '../feedback/hooks.web';
|
||||
import { setGifMenuVisibility } from '../gifs/actions';
|
||||
import { isGifEnabled } from '../gifs/function.any';
|
||||
import InviteButton from '../invite/components/add-people-dialog/web/InviteButton';
|
||||
import { registerShortcut, unregisterShortcut } from '../keyboard-shortcuts/actions.any';
|
||||
import { registerShortcut, unregisterShortcut } from '../keyboard-shortcuts/actions.web';
|
||||
import { useKeyboardShortcutsButton } from '../keyboard-shortcuts/hooks.web';
|
||||
import NoiseSuppressionButton from '../noise-suppression/components/NoiseSuppressionButton';
|
||||
import {
|
||||
@@ -37,13 +37,13 @@ import { toggleReactionsMenuVisibility } from '../reactions/actions.web';
|
||||
import RaiseHandContainerButton from '../reactions/components/web/RaiseHandContainerButtons';
|
||||
import { REACTIONS } from '../reactions/constants';
|
||||
import { shouldDisplayReactionsButtons } from '../reactions/functions.any';
|
||||
import { useReactionsButton } from '../reactions/hooks';
|
||||
import { useReactionsButton } from '../reactions/hooks.web';
|
||||
import { useLiveStreamingButton, useRecordingButton } from '../recording/hooks.web';
|
||||
import { isSalesforceEnabled } from '../salesforce/functions';
|
||||
import { startScreenShareFlow } from '../screen-share/actions.web';
|
||||
import ShareAudioButton from '../screen-share/components/web/ShareAudioButton';
|
||||
import { isScreenAudioSupported, isScreenVideoShared } from '../screen-share/functions';
|
||||
import { useSecurityDialogButton } from '../security/hooks';
|
||||
import { useSecurityDialogButton } from '../security/hooks.web';
|
||||
import SettingsButton from '../settings/components/web/SettingsButton';
|
||||
import SharedVideoButton from '../shared-video/components/web/SharedVideoButton';
|
||||
import SpeakerStats from '../speaker-stats/components/web/SpeakerStats';
|
||||
@@ -51,7 +51,7 @@ import { isSpeakerStatsDisabled } from '../speaker-stats/functions';
|
||||
import { useSpeakerStatsButton } from '../speaker-stats/hooks.web';
|
||||
import { useClosedCaptionButton } from '../subtitles/hooks.web';
|
||||
import { toggleTileView } from '../video-layout/actions.any';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.any';
|
||||
import { shouldDisplayTileView } from '../video-layout/functions.web';
|
||||
import { useTileViewButton } from '../video-layout/hooks';
|
||||
import VideoQualityButton from '../video-quality/components/VideoQualityButton.web';
|
||||
import VideoQualityDialog from '../video-quality/components/VideoQualityDialog.web';
|
||||
|
||||
@@ -108,4 +108,3 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MediaType
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -72,26 +72,26 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
const { conference } = action;
|
||||
|
||||
if (getState()['features/visitors'].iAmVisitor) {
|
||||
dispatch(openDialog(JoinMeetingDialog));
|
||||
|
||||
const { demoteActorDisplayName } = getState()['features/visitors'];
|
||||
|
||||
dispatch(setVisitorDemoteActor(undefined));
|
||||
|
||||
const notificationParams: INotificationProps = {
|
||||
titleKey: 'visitors.notification.title',
|
||||
descriptionKey: 'visitors.notification.description'
|
||||
};
|
||||
|
||||
if (demoteActorDisplayName) {
|
||||
notificationParams.descriptionKey = 'visitors.notification.demoteDescription';
|
||||
notificationParams.descriptionArguments = {
|
||||
actor: demoteActorDisplayName
|
||||
const notificationParams: INotificationProps = {
|
||||
titleKey: 'visitors.notification.title',
|
||||
descriptionKey: 'visitors.notification.demoteDescription',
|
||||
descriptionArguments: {
|
||||
actor: demoteActorDisplayName
|
||||
}
|
||||
};
|
||||
|
||||
batch(() => {
|
||||
dispatch(showNotification(notificationParams, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
dispatch(setVisitorDemoteActor(undefined));
|
||||
});
|
||||
} else {
|
||||
dispatch(openDialog(JoinMeetingDialog));
|
||||
}
|
||||
|
||||
// check for demote actor and update notification
|
||||
dispatch(showNotification(notificationParams, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
} else {
|
||||
dispatch(setVisitorsSupported(conference.isVisitorsSupported()));
|
||||
conference.on(JitsiConferenceEvents.VISITORS_SUPPORTED_CHANGED, (value: boolean) => {
|
||||
|
||||
@@ -69,10 +69,12 @@ MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: AnyA
|
||||
const existingCollabDetails = getCollabDetails(state);
|
||||
const enforceUserLimit = shouldEnforceUserLimit(state);
|
||||
const notifyUserLimit = shouldNotifyUserLimit(state);
|
||||
const iAmRecorder = Boolean(state['features/base/config'].iAmRecorder);
|
||||
|
||||
if (enforceUserLimit) {
|
||||
dispatch(restrictWhiteboard(false));
|
||||
dispatch(openDialog(WhiteboardLimitDialog));
|
||||
iAmRecorder && setTimeout(() => dispatch(hideDialog(WhiteboardLimitDialog)), 3000);
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ globalNS.connectionTimes = {
|
||||
'index.loaded': window.indexLoadedTime
|
||||
};
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
globalNS.connectionTimes['window.loaded'] = window.loadedEventTime;
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const now = window.performance.now();
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ local muc = module:depends("muc");
|
||||
local NS_NICK = 'http://jabber.org/protocol/nick';
|
||||
local is_healthcheck_room = util.is_healthcheck_room;
|
||||
|
||||
local POLLS_LIMIT = 128;
|
||||
local POLL_PAYLOAD_LIMIT = 1024;
|
||||
|
||||
-- Checks if the given stanza contains a JSON message,
|
||||
-- and that the message type pertains to the polls feature.
|
||||
-- If yes, returns the parsed message. Otherwise, returns nil.
|
||||
@@ -22,6 +25,10 @@ local function get_poll_message(stanza)
|
||||
if json_data == nil then
|
||||
return nil;
|
||||
end
|
||||
if string.len(json_data) >= POLL_PAYLOAD_LIMIT then
|
||||
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
|
||||
return nil;
|
||||
end
|
||||
local data, error = json.decode(json_data);
|
||||
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
|
||||
if error then
|
||||
@@ -72,6 +79,7 @@ module:hook("muc-room-created", function(event)
|
||||
room.polls = {
|
||||
by_id = {};
|
||||
order = {};
|
||||
count = 0;
|
||||
};
|
||||
end);
|
||||
|
||||
@@ -100,6 +108,11 @@ module:hook("message/bare", function(event)
|
||||
return
|
||||
end
|
||||
|
||||
if room.polls.count >= POLLS_LIMIT then
|
||||
module:log("error", "Too many polls created in %s", room.jid)
|
||||
return
|
||||
end
|
||||
|
||||
local answers = {}
|
||||
local compact_answers = {}
|
||||
for i, name in ipairs(data.answers) do
|
||||
@@ -117,6 +130,7 @@ module:hook("message/bare", function(event)
|
||||
|
||||
room.polls.by_id[data.pollId] = poll
|
||||
table.insert(room.polls.order, poll)
|
||||
room.polls.count = room.polls.count + 1;
|
||||
|
||||
local pollData = {
|
||||
event = event,
|
||||
|
||||
Reference in New Issue
Block a user