mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-05-30 19:07:50 +00:00
Compare commits
26 Commits
7387
...
saghul-pat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a62cddff73 | ||
|
|
555be6c229 | ||
|
|
3f5e6883b5 | ||
|
|
ddcf90e95c | ||
|
|
683e9e72b9 | ||
|
|
123eaf77fa | ||
|
|
bb29f20a07 | ||
|
|
866390ece1 | ||
|
|
5b844e45e3 | ||
|
|
1c80771405 | ||
|
|
d42cbbd9f8 | ||
|
|
18873a9659 | ||
|
|
7859397790 | ||
|
|
bc23f9cd33 | ||
|
|
02f0057578 | ||
|
|
63761d515a | ||
|
|
5cc4b31f35 | ||
|
|
a03bf2cb8e | ||
|
|
312902ea77 | ||
|
|
961a9236fd | ||
|
|
364e63da14 | ||
|
|
27c62b3d78 | ||
|
|
51623b47f0 | ||
|
|
824cfc0c9c | ||
|
|
398e170e2d | ||
|
|
f1de6887cd |
@@ -81,7 +81,8 @@ dependencies {
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||
|
||||
if (!rootProject.ext.libreBuild) {
|
||||
implementation 'com.google.android.gms:play-services-auth:16.0.1'
|
||||
// Sync with react-native-google-signin
|
||||
implementation 'com.google.android.gms:play-services-auth:19.0.2'
|
||||
|
||||
// Firebase
|
||||
// - Crashlytics
|
||||
|
||||
@@ -29,6 +29,10 @@ if [[ $MVN_HTTP == 1 ]]; then
|
||||
# Push React Native
|
||||
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
|
||||
cat react-native-${RN_VERSION}.pom \
|
||||
| sed "s#<packaging>pom</packaging>#<packaging>aar</packaging>#" \
|
||||
| sed "/<optional>/d" \
|
||||
> react-native-${RN_VERSION}-fixed.pom
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
@@ -36,7 +40,7 @@ if [[ $MVN_HTTP == 1 ]]; then
|
||||
-Dfile=react-native-${RN_VERSION}-release.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom || true
|
||||
-DpomFile=react-native-${RN_VERSION}-fixed.pom || true
|
||||
popd
|
||||
# Push JSC
|
||||
echo "Pushing JSC ${JSC_VERSION} to the Maven repo"
|
||||
@@ -55,13 +59,17 @@ else
|
||||
if [[ ! -d ${MVN_REPO}/com/facebook/react/react-native/${RN_VERSION} ]]; then
|
||||
echo "Pushing React Native ${RN_VERSION} to the Maven repo"
|
||||
pushd ${THIS_DIR}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
|
||||
cat react-native-${RN_VERSION}.pom \
|
||||
| sed "s#<packaging>pom</packaging>#<packaging>aar</packaging>#" \
|
||||
| sed "/<optional>/d" \
|
||||
> react-native-${RN_VERSION}-fixed.pom
|
||||
mvn \
|
||||
deploy:deploy-file \
|
||||
-Durl=${MVN_REPO} \
|
||||
-Dfile=react-native-${RN_VERSION}-release.aar \
|
||||
-Dpackaging=aar \
|
||||
-DgeneratePom=false \
|
||||
-DpomFile=react-native-${RN_VERSION}.pom
|
||||
-DpomFile=react-native-${RN_VERSION}-fixed.pom
|
||||
popd
|
||||
fi
|
||||
|
||||
|
||||
298
conference.js
298
conference.js
@@ -4,12 +4,8 @@ import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from '@jitsi/logger';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import { openConnection } from './connection';
|
||||
import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
|
||||
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from './modules/UI/UIErrors';
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import UIUtil from './modules/UI/util/UIUtil';
|
||||
import VideoLayout from './modules/UI/videolayout/VideoLayout';
|
||||
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
|
||||
import Recorder from './modules/recorder/Recorder';
|
||||
import { createTaskQueue } from './modules/util/helpers';
|
||||
@@ -37,7 +33,7 @@ import {
|
||||
conferenceSubjectChanged,
|
||||
conferenceTimestampChanged,
|
||||
conferenceUniqueIdSet,
|
||||
conferenceWillJoin,
|
||||
conferenceWillInit,
|
||||
conferenceWillLeave,
|
||||
dataChannelClosed,
|
||||
dataChannelOpened,
|
||||
@@ -57,12 +53,10 @@ import {
|
||||
commonUserJoinedHandling,
|
||||
commonUserLeftHandling,
|
||||
getConferenceOptions,
|
||||
getVisitorOptions,
|
||||
restoreConferenceOptions,
|
||||
sendLocalParticipant
|
||||
} from './react/features/base/conference/functions';
|
||||
import { overwriteConfig } from './react/features/base/config/actions';
|
||||
import { getReplaceParticipant } from './react/features/base/config/functions';
|
||||
import { connect } from './react/features/base/connection/actions.web';
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
@@ -77,15 +71,12 @@ import {
|
||||
import {
|
||||
JitsiConferenceErrors,
|
||||
JitsiConferenceEvents,
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents,
|
||||
JitsiE2ePingEvents,
|
||||
JitsiMediaDevicesEvents,
|
||||
JitsiTrackErrors,
|
||||
JitsiTrackEvents,
|
||||
browser
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
|
||||
import {
|
||||
gumPending,
|
||||
setAudioAvailable,
|
||||
@@ -145,7 +136,12 @@ import { maybeOpenFeedbackDialog, submitFeedback } from './react/features/feedba
|
||||
import { initKeyboardShortcuts } from './react/features/keyboard-shortcuts/actions';
|
||||
import { maybeSetLobbyChatMessageListener } from './react/features/lobby/actions.any';
|
||||
import { setNoiseSuppressionEnabled } from './react/features/noise-suppression/actions';
|
||||
import { hideNotification, showNotification, showWarningNotification } from './react/features/notifications/actions';
|
||||
import {
|
||||
hideNotification,
|
||||
showErrorNotification,
|
||||
showNotification,
|
||||
showWarningNotification
|
||||
} from './react/features/notifications/actions';
|
||||
import {
|
||||
DATA_CHANNEL_CLOSED_NOTIFICATION_ID,
|
||||
NOTIFICATION_TIMEOUT_TYPE
|
||||
@@ -153,7 +149,7 @@ import {
|
||||
import { isModerationNotificationDisplayed } from './react/features/notifications/functions';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay/actions';
|
||||
import { suspendDetected } from './react/features/power-monitor/actions';
|
||||
import { initPrejoin, makePrecallTest, setJoiningInProgress } from './react/features/prejoin/actions';
|
||||
import { initPrejoin, makePrecallTest } from './react/features/prejoin/actions';
|
||||
import { isPrejoinPageVisible } from './react/features/prejoin/functions';
|
||||
import { disableReceiver, stopReceiver } from './react/features/remote-control/actions';
|
||||
import { setScreenAudioShareState } from './react/features/screen-share/actions.web';
|
||||
@@ -164,7 +160,6 @@ import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise'
|
||||
import { endpointMessageReceived } from './react/features/subtitles/actions.any';
|
||||
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
|
||||
import { muteLocal } from './react/features/video-menu/actions.any';
|
||||
import { setIAmVisitor } from './react/features/visitors/actions';
|
||||
import { iAmVisitor } from './react/features/visitors/functions';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
|
||||
@@ -173,25 +168,6 @@ const logger = Logger.getLogger(__filename);
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
let room;
|
||||
let connection;
|
||||
|
||||
/**
|
||||
* The promise is used when the prejoin screen is shown.
|
||||
* While the user configures the devices the connection can be made.
|
||||
*
|
||||
* @type {Promise<Object>}
|
||||
* @private
|
||||
*/
|
||||
let _connectionPromise;
|
||||
|
||||
/**
|
||||
* We are storing the resolve function of a Promise that waits for the _connectionPromise to be created. This is needed
|
||||
* when the prejoin button was pressed before the conference object was initialized and the _connectionPromise has not
|
||||
* been initialized when we tried to execute prejoinStart. In this case in prejoinStart we create a new Promise, assign
|
||||
* the resolve function to this variable and wait for the promise to resolve before we continue. The
|
||||
* _onConnectionPromiseCreated will be called once the _connectionPromise is created.
|
||||
*/
|
||||
let _onConnectionPromiseCreated;
|
||||
|
||||
/*
|
||||
* Logic to open a desktop picker put on the window global for
|
||||
@@ -213,26 +189,6 @@ const commands = {
|
||||
ETHERPAD: 'etherpad'
|
||||
};
|
||||
|
||||
/**
|
||||
* Open Connection. When authentication failed it shows auth dialog.
|
||||
* @param roomName the room name to use
|
||||
* @returns Promise<JitsiConnection>
|
||||
*/
|
||||
function connect(roomName) {
|
||||
return openConnection({
|
||||
retry: true,
|
||||
roomName
|
||||
})
|
||||
.catch(err => {
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
APP.UI.notifyTokenAuthFailed();
|
||||
} else {
|
||||
APP.UI.notifyConnectionFailed(err);
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Share data to other users.
|
||||
* @param command the command
|
||||
@@ -326,63 +282,25 @@ class ConferenceConnector {
|
||||
break;
|
||||
}
|
||||
|
||||
// not enough rights to create conference
|
||||
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED: {
|
||||
|
||||
const replaceParticipant = getReplaceParticipant(APP.store.getState());
|
||||
|
||||
// Schedule reconnect to check if someone else created the room.
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
APP.store.dispatch(conferenceWillJoin(room));
|
||||
room.join(null, replaceParticipant);
|
||||
}, 5000);
|
||||
|
||||
const { password }
|
||||
= APP.store.getState()['features/base/conference'];
|
||||
|
||||
AuthHandler.requireAuth(room, password);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.RESERVATION_ERROR: {
|
||||
const [ code, msg ] = params;
|
||||
|
||||
APP.UI.notifyReservationError(code, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
const newConfig = getVisitorOptions(APP.store.getState(), params);
|
||||
|
||||
if (!newConfig) {
|
||||
logger.warn('Not redirected missing params');
|
||||
break;
|
||||
}
|
||||
|
||||
const [ vnode ] = params;
|
||||
|
||||
APP.store.dispatch(overwriteConfig(newConfig))
|
||||
.then(() => this._conference.leaveRoom())
|
||||
.then(() => APP.store.dispatch(setIAmVisitor(Boolean(vnode))))
|
||||
|
||||
// we do not clear local tracks on error, so we need to manually clear them
|
||||
.then(() => APP.store.dispatch(destroyLocalTracks()))
|
||||
.then(() => {
|
||||
// Reset VideoLayout. It's destroyed in features/video-layout/middleware.web.js so re-initialize it.
|
||||
VideoLayout.initLargeVideo();
|
||||
VideoLayout.resizeVideoArea();
|
||||
|
||||
connect(this._conference.roomName).then(con => {
|
||||
this._conference.startConference(con, []);
|
||||
});
|
||||
});
|
||||
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionArguments: {
|
||||
code,
|
||||
msg
|
||||
},
|
||||
descriptionKey: 'dialog.reservationErrorMsg',
|
||||
titleKey: 'dialog.reservationError'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
break;
|
||||
}
|
||||
|
||||
case JitsiConferenceErrors.GRACEFUL_SHUTDOWN:
|
||||
APP.UI.notifyGracefulShutdown();
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.gracefulShutdown',
|
||||
titleKey: 'dialog.serviceUnavailable'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
break;
|
||||
|
||||
// FIXME FOCUS_DISCONNECTED is a confusing event name.
|
||||
@@ -408,31 +326,9 @@ class ConferenceConnector {
|
||||
// FIXME the conference should be stopped by the library and not by
|
||||
// the app. Both the errors above are unrecoverable from the library
|
||||
// perspective.
|
||||
room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).then(() => connection.disconnect());
|
||||
room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).then(() => APP.connection.disconnect());
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
|
||||
APP.UI.notifyMaxUsersLimitReached();
|
||||
|
||||
// in case of max users(it can be from a visitor node), let's restore
|
||||
// oldConfig if any as we will be back to the main prosody
|
||||
const newConfig = restoreConferenceOptions(APP.store.getState());
|
||||
|
||||
if (newConfig) {
|
||||
APP.store.dispatch(overwriteConfig(newConfig))
|
||||
.then(() => this._conference.leaveRoom())
|
||||
.then(() => {
|
||||
_connectionPromise = connect(this._conference.roomName);
|
||||
|
||||
return _connectionPromise;
|
||||
})
|
||||
.then(con => {
|
||||
APP.connection = connection = con;
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS:
|
||||
APP.store.dispatch(reloadWithStoredParams());
|
||||
break;
|
||||
@@ -488,11 +384,11 @@ function disconnect() {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
if (!connection) {
|
||||
if (!APP.connection) {
|
||||
return onDisconnected();
|
||||
}
|
||||
|
||||
return connection.disconnect().then(onDisconnected, onDisconnected);
|
||||
return APP.connection.disconnect().then(onDisconnected, onDisconnected);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -510,25 +406,6 @@ function setGUMPendingStateOnFailedTracks(tracks) {
|
||||
APP.store.dispatch(gumPending(nonPendingTracks, IGUMPendingState.NONE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles CONNECTION_FAILED events from lib-jitsi-meet.
|
||||
*
|
||||
* @param {JitsiConnectionError} error - The reported error.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function _connectionFailedHandler(error) {
|
||||
if (isFatalJitsiConnectionError(error)) {
|
||||
APP.connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_connectionFailedHandler);
|
||||
if (room) {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
room.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Flag used to delay modification of the muted status of local media tracks
|
||||
@@ -547,7 +424,16 @@ export default {
|
||||
/**
|
||||
* Returns an object containing a promise which resolves with the created tracks &
|
||||
* the errors resulting from that process.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {boolean} options.startAudioOnly=false - if <tt>true</tt> then
|
||||
* only audio track will be created and the audio only mode will be turned
|
||||
* on.
|
||||
* @param {boolean} options.startScreenSharing=false - if <tt>true</tt>
|
||||
* should start with screensharing instead of camera video.
|
||||
* @param {boolean} options.startWithAudioMuted - will start the conference
|
||||
* without any audio tracks.
|
||||
* @param {boolean} options.startWithVideoMuted - will start the conference
|
||||
* without any video tracks.
|
||||
* @returns {Promise<JitsiLocalTrack[]>, Object}
|
||||
*/
|
||||
createInitialLocalTracks(options = {}) {
|
||||
@@ -725,37 +611,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates local media tracks and connects to a room. Will show error
|
||||
* dialogs in case accessing the local microphone and/or camera failed. Will
|
||||
* show guidance overlay for users on how to give access to camera and/or
|
||||
* microphone.
|
||||
* @param {string} roomName
|
||||
* @param {object} options
|
||||
* @param {boolean} options.startAudioOnly=false - if <tt>true</tt> then
|
||||
* only audio track will be created and the audio only mode will be turned
|
||||
* on.
|
||||
* @param {boolean} options.startScreenSharing=false - if <tt>true</tt>
|
||||
* should start with screensharing instead of camera video.
|
||||
* @param {boolean} options.startWithAudioMuted - will start the conference
|
||||
* without any audio tracks.
|
||||
* @param {boolean} options.startWithVideoMuted - will start the conference
|
||||
* without any video tracks.
|
||||
* @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
|
||||
*/
|
||||
createInitialLocalTracksAndConnect(roomName, options = {}) {
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(options);
|
||||
|
||||
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
||||
.then(([ tracks, con ]) => {
|
||||
|
||||
this._displayErrorsForCreateInitialLocalTracks(errors);
|
||||
|
||||
return [ tracks, con ];
|
||||
});
|
||||
},
|
||||
|
||||
startConference(con, tracks) {
|
||||
startConference(tracks) {
|
||||
tracks.forEach(track => {
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
|
||||
@@ -768,9 +624,6 @@ export default {
|
||||
}
|
||||
});
|
||||
|
||||
con.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _connectionFailedHandler);
|
||||
APP.connection = connection = con;
|
||||
|
||||
this._createRoom(tracks);
|
||||
|
||||
// if user didn't give access to mic or camera or doesn't have
|
||||
@@ -860,17 +713,6 @@ export default {
|
||||
};
|
||||
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
_connectionPromise = connect(roomName).then(c => {
|
||||
// We want to initialize it early, in case of errors to be able to gather logs.
|
||||
APP.connection = c;
|
||||
|
||||
return c;
|
||||
});
|
||||
|
||||
if (_onConnectionPromiseCreated) {
|
||||
_onConnectionPromiseCreated();
|
||||
}
|
||||
|
||||
APP.store.dispatch(makePrecallTest(this._getConferenceOptions()));
|
||||
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
@@ -897,41 +739,26 @@ export default {
|
||||
return this._setLocalAudioVideoStreams(tracks);
|
||||
}
|
||||
|
||||
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
|
||||
const { tryCreateLocalTracks, errors } = this.createInitialLocalTracks(initialOptions);
|
||||
|
||||
this._initDeviceList(true);
|
||||
return Promise.all([
|
||||
tryCreateLocalTracks.then(tr => {
|
||||
this._displayErrorsForCreateInitialLocalTracks(errors);
|
||||
|
||||
const filteredTracks = handleInitialTracks(initialOptions, tracks);
|
||||
return tr;
|
||||
}).then(tr => {
|
||||
this._initDeviceList(true);
|
||||
|
||||
setGUMPendingStateOnFailedTracks(filteredTracks);
|
||||
const filteredTracks = handleInitialTracks(initialOptions, tr);
|
||||
|
||||
return this.startConference(con, filteredTracks);
|
||||
},
|
||||
setGUMPendingStateOnFailedTracks(filteredTracks);
|
||||
|
||||
/**
|
||||
* Joins conference after the tracks have been configured in the prejoin screen.
|
||||
*
|
||||
* @param {Object[]} tracks - An array with the configured tracks
|
||||
* @returns {void}
|
||||
*/
|
||||
async prejoinStart(tracks) {
|
||||
if (!_connectionPromise) {
|
||||
// The conference object isn't initialized yet. Wait for the promise to initialise.
|
||||
await new Promise(resolve => {
|
||||
_onConnectionPromiseCreated = resolve;
|
||||
});
|
||||
_onConnectionPromiseCreated = undefined;
|
||||
}
|
||||
|
||||
let con;
|
||||
|
||||
try {
|
||||
con = await _connectionPromise;
|
||||
this.startConference(con, tracks);
|
||||
} catch (error) {
|
||||
logger.error(`An error occurred while trying to join a meeting from the prejoin screen: ${error}`);
|
||||
APP.store.dispatch(setJoiningInProgress(false));
|
||||
}
|
||||
return filteredTracks;
|
||||
}),
|
||||
APP.store.dispatch(connect())
|
||||
]).then(([ tracks, _ ]) => {
|
||||
this.startConference(tracks).catch(logger.error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1413,9 +1240,7 @@ export default {
|
||||
* Used by the Breakout Rooms feature to join a breakout room or go back to the main room.
|
||||
*/
|
||||
async joinRoom(roomName, options) {
|
||||
// Reset VideoLayout. It's destroyed in features/video-layout/middleware.web.js so re-initialize it.
|
||||
VideoLayout.initLargeVideo();
|
||||
VideoLayout.resizeVideoArea();
|
||||
APP.store.dispatch(conferenceWillInit());
|
||||
|
||||
// Restore initial state.
|
||||
this._localTracksInitialized = false;
|
||||
@@ -1440,7 +1265,7 @@ export default {
|
||||
},
|
||||
|
||||
_createRoom(localTracks) {
|
||||
room = connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
|
||||
room = APP.connection.initJitsiConference(APP.conference.roomName, this._getConferenceOptions());
|
||||
|
||||
// Filter out the tracks that are muted (except on Safari).
|
||||
const tracks = browser.isWebKitBased() ? localTracks : localTracks.filter(track => !track.isMuted());
|
||||
@@ -1787,10 +1612,10 @@ export default {
|
||||
titleKey = 'notify.screenShareNoAudioTitle';
|
||||
}
|
||||
|
||||
APP.UI.messageHandler.showError({
|
||||
APP.store.dispatch(showErrorNotification({
|
||||
descriptionKey,
|
||||
titleKey
|
||||
});
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2184,21 +2009,6 @@ export default {
|
||||
this.hangup(true);
|
||||
});
|
||||
|
||||
// logout
|
||||
APP.UI.addListener(UIEvents.LOGOUT, () => {
|
||||
AuthHandler.logout(room).then(url => {
|
||||
if (url) {
|
||||
UIUtil.redirect(url);
|
||||
} else {
|
||||
this.hangup(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
||||
AuthHandler.authenticate(room);
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
UIEvents.VIDEO_DEVICE_CHANGED,
|
||||
cameraDeviceId => {
|
||||
|
||||
209
connection.js
209
connection.js
@@ -1,209 +0,0 @@
|
||||
/* global APP, JitsiMeetJS, config */
|
||||
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from '@jitsi/logger';
|
||||
|
||||
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
|
||||
import { LoginDialog } from './react/features/authentication/components';
|
||||
import { isTokenAuthEnabled } from './react/features/authentication/functions';
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
constructOptions
|
||||
} from './react/features/base/connection/actions.web';
|
||||
import { openDialog } from './react/features/base/dialog/actions';
|
||||
import { setJWT } from './react/features/base/jwt/actions';
|
||||
import {
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { isFatalJitsiConnectionError } from './react/features/base/lib-jitsi-meet/functions';
|
||||
import { getCustomerDetails } from './react/features/jaas/actions.any';
|
||||
import { getJaasJWT, isVpaasMeeting } from './react/features/jaas/functions';
|
||||
import {
|
||||
setPrejoinDisplayNameRequired
|
||||
} from './react/features/prejoin/actions';
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* The feature announced so we can distinguish jibri participants.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
|
||||
|
||||
/**
|
||||
* Try to open connection using provided credentials.
|
||||
* @param {string} [id]
|
||||
* @param {string} [password]
|
||||
* @returns {Promise<JitsiConnection>} connection if
|
||||
* everything is ok, else error.
|
||||
*/
|
||||
export async function connect(id, password) {
|
||||
const state = APP.store.getState();
|
||||
let { jwt } = state['features/base/jwt'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
|
||||
if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
|
||||
await APP.store.dispatch(getCustomerDetails());
|
||||
|
||||
if (!jwt) {
|
||||
jwt = await getJaasJWT(state);
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
}
|
||||
}
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(null, jwt, constructOptions(state));
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
|
||||
displayNameRequiredHandler
|
||||
);
|
||||
|
||||
/* eslint-disable max-params */
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function connectionFailedHandler(error, message, credentials, details) {
|
||||
/* eslint-enable max-params */
|
||||
APP.store.dispatch(
|
||||
connectionFailed(
|
||||
connection, {
|
||||
credentials,
|
||||
details,
|
||||
message,
|
||||
name: error
|
||||
}));
|
||||
|
||||
if (isFatalJitsiConnectionError(error)) {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
connectionFailedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
handleConnectionEstablished);
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
handleConnectionFailed);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function handleConnectionEstablished() {
|
||||
APP.store.dispatch(connectionEstablished(connection, Date.now()));
|
||||
unsubscribe();
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function handleConnectionFailed(err) {
|
||||
unsubscribe();
|
||||
logger.error('CONNECTION FAILED:', err);
|
||||
reject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the display name for the prejoin screen as required.
|
||||
* This can happen if a user tries to join a room with lobby enabled.
|
||||
*/
|
||||
function displayNameRequiredHandler() {
|
||||
APP.store.dispatch(setPrejoinDisplayNameRequired());
|
||||
}
|
||||
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open JitsiConnection using provided credentials.
|
||||
* If retry option is true it will show auth dialog on PASSWORD_REQUIRED error.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {string} [options.id]
|
||||
* @param {string} [options.password]
|
||||
* @param {string} [options.roomName]
|
||||
* @param {boolean} [retry] if we should show auth dialog
|
||||
* on PASSWORD_REQUIRED error.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function openConnection({ id, password, retry, roomName }) {
|
||||
const usernameOverride
|
||||
= jitsiLocalStorage.getItem('xmpp_username_override');
|
||||
const passwordOverride
|
||||
= jitsiLocalStorage.getItem('xmpp_password_override');
|
||||
|
||||
if (usernameOverride && usernameOverride.length > 0) {
|
||||
id = usernameOverride; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
if (passwordOverride && passwordOverride.length > 0) {
|
||||
password = passwordOverride; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
return connect(id, password).catch(err => {
|
||||
if (retry) {
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
return requestAuth(roomName);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Authentication Dialog and try to connect with new credentials.
|
||||
* If failed to connect because of PASSWORD_REQUIRED error
|
||||
* then ask for password again.
|
||||
* @param {string} [roomName] name of the conference room
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
function requestAuth(roomName) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
// This Promise never resolves as user gets redirected to another URL
|
||||
return new Promise(() => redirectToTokenAuthService(roomName));
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
const onSuccess = connection => {
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -66,10 +66,6 @@ body, input, textarea, keygen, select, button {
|
||||
font-family: $baseFontFamily !important;
|
||||
}
|
||||
|
||||
#nowebrtc {
|
||||
display:none;
|
||||
}
|
||||
|
||||
button, input, select, textarea {
|
||||
margin: 0;
|
||||
vertical-align: baseline;
|
||||
@@ -148,21 +144,6 @@ form {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.connected {
|
||||
color: #21B9FC;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.lastN, .disconnected {
|
||||
color: #a3a3a3;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-style default OS scrollbar.
|
||||
*/
|
||||
@@ -193,3 +174,16 @@ form {
|
||||
.jitsi-icon svg path {
|
||||
fill: inherit !important;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
175
css/_chat.scss
175
css/_chat.scss
@@ -1,35 +1,3 @@
|
||||
#sideToolbarContainer {
|
||||
background-color: $chatBackgroundColor;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .16s ease-in-out;
|
||||
width: $sidebarWidth;
|
||||
z-index: 300;
|
||||
|
||||
@media (max-width: 580px) {
|
||||
height: 100vh;
|
||||
height: -webkit-fill-available;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// extract header + tabs height
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
|
||||
.chat-panel-no-tabs {
|
||||
// extract header height
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
#chat-conversation-container {
|
||||
// extract message input height
|
||||
height: calc(100% - 64px);
|
||||
@@ -76,27 +44,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chat-header {
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
|
||||
.jitsi-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 24px;
|
||||
}
|
||||
@@ -112,61 +59,6 @@
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.smiley-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
margin: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#chat-input .smiley-button {
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: #484A4F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
.usrmsg-form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
-ms-overflow-style: none;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
scrollbar-width: none;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg:hover {
|
||||
border: 0px none;
|
||||
box-shadow: none;
|
||||
}
|
||||
#usermsg:focus,
|
||||
#usermsg:active {
|
||||
border-bottom: 1px solid white;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
#nickname {
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
@@ -174,11 +66,6 @@
|
||||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
#nickname-title {
|
||||
margin-bottom: 5px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
label[for="nickinput"] {
|
||||
> div > span {
|
||||
color: #B8C7E0;
|
||||
@@ -191,24 +78,6 @@
|
||||
label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
background: #1B67EC;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #AFB6BC;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
@@ -216,14 +85,6 @@
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.chatmessage .usermessage {
|
||||
@@ -231,32 +92,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
* {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0 !important;
|
||||
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||
clip-path: inset(50%) !important;
|
||||
height: 1px !important;
|
||||
margin: -1px !important;
|
||||
overflow: hidden !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
width: 1px !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
&.localuser {
|
||||
background-color: #484A4F;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-radius: 0px;
|
||||
|
||||
@@ -288,13 +124,6 @@
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#smileysarea {
|
||||
display: flex;
|
||||
max-height: 150px;
|
||||
min-height: 35px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.smiley-input {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
@@ -345,10 +174,6 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
@@ -28,18 +28,6 @@
|
||||
margin-bottom: env(safe-area-inset-bottom, 0);
|
||||
width: 100%;
|
||||
|
||||
.drawer-toggle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
fill: none;
|
||||
}
|
||||
}
|
||||
|
||||
&#{&} .overflow-menu {
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
@@ -65,25 +53,10 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
#e2ee-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.description {
|
||||
font-size: 13px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
margin-top: 15px;
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,4 @@
|
||||
/*Initialize*/
|
||||
div.loginmenu {
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
top: 40px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
a.disabled {
|
||||
color: gray !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loginmenu.extendedToolbarPopup {
|
||||
top: 20px;
|
||||
left: 40px;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
.filmstrip-toolbox,
|
||||
.always-on-top-toolbox {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-radius: 3px;
|
||||
@@ -29,7 +28,3 @@
|
||||
transform: translateX(-50%);
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.filmstrip-toolbox {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
.jqistates {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.jqistates h2 {
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
font-size: 18px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
.jqistates input {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.jqistates input[type='text'], input[type='password'] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button.jqidefaultbutton #inviteLinkRef {
|
||||
color: #2c8ad2;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
@@ -75,6 +75,3 @@
|
||||
margin-bottom: 36px;
|
||||
width: 100%;
|
||||
}
|
||||
.navigate-section-list-empty {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.notice {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
z-index: $zindex3;
|
||||
margin-top: 6px;
|
||||
|
||||
@include transform(translateX(-50%));
|
||||
|
||||
&__message {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.polls-panel {
|
||||
height: calc(100% - 119px);
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
.recordingSpinner {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.recording-dialog {
|
||||
flex: 0;
|
||||
flex-direction: column;
|
||||
@@ -50,10 +46,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.recording-switch-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.recording-icon-container {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -156,8 +148,7 @@
|
||||
*/
|
||||
font-size: 14px;
|
||||
|
||||
.broadcast-dropdown,
|
||||
.broadcast-dropdown-trigger {
|
||||
.broadcast-dropdown {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,14 +34,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.subject-info {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
max-width: 80%;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.details-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -58,21 +58,6 @@
|
||||
z-index: $toolbarZ;
|
||||
pointer-events: none;
|
||||
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -112,16 +97,6 @@
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: #36383C;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
padding: 0 4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.overflow-menu-hr {
|
||||
border-top: 1px solid #4C4D50;
|
||||
border-bottom: 0;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,24 +24,6 @@
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows an inline element.
|
||||
*/
|
||||
.show-inline {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a flex element.
|
||||
*/
|
||||
.show-flex {
|
||||
display: -webkit-box !important;
|
||||
display: -moz-box !important;
|
||||
display: -ms-flexbox !important;
|
||||
display: -webkit-flex !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* resets default button styles,
|
||||
* mostly intended to be used on interactive elements that
|
||||
|
||||
@@ -209,23 +209,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
#reloadPresentation {
|
||||
display: none;
|
||||
position: absolute;
|
||||
color: #FFFFFF;
|
||||
top: 0;
|
||||
right:0;
|
||||
padding: 10px 10px;
|
||||
font-size: 11pt;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
background-clip: padding-box;
|
||||
-webkit-border-radius: 5px;
|
||||
-webkit-background-clip: padding-box;
|
||||
z-index: 20; /*The reload button should appear on top of the header!*/
|
||||
}
|
||||
|
||||
#dominantSpeaker {
|
||||
visibility: hidden;
|
||||
width: 300px;
|
||||
@@ -236,10 +219,6 @@
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#mixedstream {
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
#dominantSpeakerAvatarContainer,
|
||||
.dynamic-shadow {
|
||||
width: 200px;
|
||||
@@ -309,11 +288,6 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.videoMessageFilter {
|
||||
-webkit-filter: grayscale(.5) opacity(0.8);
|
||||
filter: grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
#remotePresenceMessage,
|
||||
#remoteConnectionMessage {
|
||||
position: absolute;
|
||||
|
||||
@@ -37,7 +37,6 @@ $flagsImagePath: "../images/";
|
||||
@import 'modals/screen-share/share-audio';
|
||||
@import 'modals/screen-share/share-screen-warning';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
@import 'subject';
|
||||
@import 'popup_menu';
|
||||
@import 'recording';
|
||||
@@ -73,12 +72,10 @@ $flagsImagePath: "../images/";
|
||||
@import 'premeeting/main';
|
||||
@import 'modals/invite/invite_more';
|
||||
@import 'modals/security/security';
|
||||
@import 'e2ee';
|
||||
@import 'responsive';
|
||||
@import 'drawer';
|
||||
@import 'participants-pane';
|
||||
@import 'reactions-menu';
|
||||
@import 'plan-limit';
|
||||
@import 'polls';
|
||||
|
||||
/* Modules END */
|
||||
|
||||
440
ios/Podfile.lock
440
ios/Podfile.lock
@@ -14,14 +14,14 @@ PODS:
|
||||
- CocoaLumberjack/Core (= 3.7.2)
|
||||
- CocoaLumberjack/Core (3.7.2)
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.69.10)
|
||||
- FBReactNativeSpec (0.69.10):
|
||||
- FBLazyVector (0.69.11)
|
||||
- FBReactNativeSpec (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.69.10)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Core (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- RCTRequired (= 0.69.11)
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Core (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- Firebase/Analytics (8.15.0):
|
||||
- Firebase/Core
|
||||
- Firebase/Core (8.15.0):
|
||||
@@ -164,203 +164,203 @@ PODS:
|
||||
- DoubleConversion
|
||||
- fmt (~> 6.2.1)
|
||||
- glog
|
||||
- RCTRequired (0.69.10)
|
||||
- RCTTypeSafety (0.69.10):
|
||||
- FBLazyVector (= 0.69.10)
|
||||
- RCTRequired (= 0.69.10)
|
||||
- React-Core (= 0.69.10)
|
||||
- React (0.69.10):
|
||||
- React-Core (= 0.69.10)
|
||||
- React-Core/DevSupport (= 0.69.10)
|
||||
- React-Core/RCTWebSocket (= 0.69.10)
|
||||
- React-RCTActionSheet (= 0.69.10)
|
||||
- React-RCTAnimation (= 0.69.10)
|
||||
- React-RCTBlob (= 0.69.10)
|
||||
- React-RCTImage (= 0.69.10)
|
||||
- React-RCTLinking (= 0.69.10)
|
||||
- React-RCTNetwork (= 0.69.10)
|
||||
- React-RCTSettings (= 0.69.10)
|
||||
- React-RCTText (= 0.69.10)
|
||||
- React-RCTVibration (= 0.69.10)
|
||||
- React-bridging (0.69.10):
|
||||
- RCTRequired (0.69.11)
|
||||
- RCTTypeSafety (0.69.11):
|
||||
- FBLazyVector (= 0.69.11)
|
||||
- RCTRequired (= 0.69.11)
|
||||
- React-Core (= 0.69.11)
|
||||
- React (0.69.11):
|
||||
- React-Core (= 0.69.11)
|
||||
- React-Core/DevSupport (= 0.69.11)
|
||||
- React-Core/RCTWebSocket (= 0.69.11)
|
||||
- React-RCTActionSheet (= 0.69.11)
|
||||
- React-RCTAnimation (= 0.69.11)
|
||||
- React-RCTBlob (= 0.69.11)
|
||||
- React-RCTImage (= 0.69.11)
|
||||
- React-RCTLinking (= 0.69.11)
|
||||
- React-RCTNetwork (= 0.69.11)
|
||||
- React-RCTSettings (= 0.69.11)
|
||||
- React-RCTText (= 0.69.11)
|
||||
- React-RCTVibration (= 0.69.11)
|
||||
- React-bridging (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-callinvoker (0.69.10)
|
||||
- React-Codegen (0.69.10):
|
||||
- FBReactNativeSpec (= 0.69.10)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-callinvoker (0.69.11)
|
||||
- React-Codegen (0.69.11):
|
||||
- FBReactNativeSpec (= 0.69.11)
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTRequired (= 0.69.10)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Core (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-Core (0.69.10):
|
||||
- RCTRequired (= 0.69.11)
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Core (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-Core (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-Core/Default (= 0.69.11)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/CoreModulesHeaders (0.69.10):
|
||||
- React-Core/CoreModulesHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/Default (0.69.10):
|
||||
- React-Core/Default (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/DevSupport (0.69.10):
|
||||
- React-Core/DevSupport (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.69.10)
|
||||
- React-Core/RCTWebSocket (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-jsinspector (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-Core/Default (= 0.69.11)
|
||||
- React-Core/RCTWebSocket (= 0.69.11)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-jsinspector (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTActionSheetHeaders (0.69.10):
|
||||
- React-Core/RCTActionSheetHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTAnimationHeaders (0.69.10):
|
||||
- React-Core/RCTAnimationHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTBlobHeaders (0.69.10):
|
||||
- React-Core/RCTBlobHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTImageHeaders (0.69.10):
|
||||
- React-Core/RCTImageHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTLinkingHeaders (0.69.10):
|
||||
- React-Core/RCTLinkingHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTNetworkHeaders (0.69.10):
|
||||
- React-Core/RCTNetworkHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTSettingsHeaders (0.69.10):
|
||||
- React-Core/RCTSettingsHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTTextHeaders (0.69.10):
|
||||
- React-Core/RCTTextHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTVibrationHeaders (0.69.10):
|
||||
- React-Core/RCTVibrationHeaders (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-Core/RCTWebSocket (0.69.10):
|
||||
- React-Core/RCTWebSocket (0.69.11):
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Core/Default (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsiexecutor (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-Core/Default (= 0.69.11)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsiexecutor (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- Yoga
|
||||
- React-CoreModules (0.69.10):
|
||||
- React-CoreModules (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/CoreModulesHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-RCTImage (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-cxxreact (0.69.10):
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/CoreModulesHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-RCTImage (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-cxxreact (0.69.11):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-callinvoker (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-jsinspector (= 0.69.10)
|
||||
- React-logger (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-runtimeexecutor (= 0.69.10)
|
||||
- React-jsi (0.69.10):
|
||||
- React-callinvoker (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-jsinspector (= 0.69.11)
|
||||
- React-logger (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- React-runtimeexecutor (= 0.69.11)
|
||||
- React-jsi (0.69.11):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsi/Default (= 0.69.10)
|
||||
- React-jsi/Default (0.69.10):
|
||||
- React-jsi/Default (= 0.69.11)
|
||||
- React-jsi/Default (0.69.11):
|
||||
- boost (= 1.76.0)
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-jsiexecutor (0.69.10):
|
||||
- React-jsiexecutor (0.69.11):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-jsinspector (0.69.10)
|
||||
- React-logger (0.69.10):
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- React-jsinspector (0.69.11)
|
||||
- React-logger (0.69.11):
|
||||
- glog
|
||||
- react-native-background-timer (2.4.1):
|
||||
- React-Core
|
||||
@@ -395,72 +395,72 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-webview (11.15.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.69.10)
|
||||
- React-RCTActionSheet (0.69.10):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.69.10)
|
||||
- React-RCTAnimation (0.69.10):
|
||||
- React-perflogger (0.69.11)
|
||||
- React-RCTActionSheet (0.69.11):
|
||||
- React-Core/RCTActionSheetHeaders (= 0.69.11)
|
||||
- React-RCTAnimation (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTAnimationHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-RCTBlob (0.69.10):
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTAnimationHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-RCTBlob (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTBlobHeaders (= 0.69.10)
|
||||
- React-Core/RCTWebSocket (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-RCTNetwork (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-RCTImage (0.69.10):
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTBlobHeaders (= 0.69.11)
|
||||
- React-Core/RCTWebSocket (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-RCTNetwork (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-RCTImage (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTImageHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-RCTNetwork (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-RCTLinking (0.69.10):
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTLinkingHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-RCTNetwork (0.69.10):
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTImageHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-RCTNetwork (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-RCTLinking (0.69.11):
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTLinkingHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-RCTNetwork (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTNetworkHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-RCTSettings (0.69.10):
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTNetworkHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-RCTSettings (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- RCTTypeSafety (= 0.69.10)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTSettingsHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-RCTText (0.69.10):
|
||||
- React-Core/RCTTextHeaders (= 0.69.10)
|
||||
- React-RCTVibration (0.69.10):
|
||||
- RCTTypeSafety (= 0.69.11)
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTSettingsHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-RCTText (0.69.11):
|
||||
- React-Core/RCTTextHeaders (= 0.69.11)
|
||||
- React-RCTVibration (0.69.11):
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-Codegen (= 0.69.10)
|
||||
- React-Core/RCTVibrationHeaders (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (= 0.69.10)
|
||||
- React-runtimeexecutor (0.69.10):
|
||||
- React-jsi (= 0.69.10)
|
||||
- ReactCommon/turbomodule/core (0.69.10):
|
||||
- React-Codegen (= 0.69.11)
|
||||
- React-Core/RCTVibrationHeaders (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (= 0.69.11)
|
||||
- React-runtimeexecutor (0.69.11):
|
||||
- React-jsi (= 0.69.11)
|
||||
- ReactCommon/turbomodule/core (0.69.11):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- RCT-Folly (= 2021.06.28.00-v2)
|
||||
- React-bridging (= 0.69.10)
|
||||
- React-callinvoker (= 0.69.10)
|
||||
- React-Core (= 0.69.10)
|
||||
- React-cxxreact (= 0.69.10)
|
||||
- React-jsi (= 0.69.10)
|
||||
- React-logger (= 0.69.10)
|
||||
- React-perflogger (= 0.69.10)
|
||||
- React-bridging (= 0.69.11)
|
||||
- React-callinvoker (= 0.69.11)
|
||||
- React-Core (= 0.69.11)
|
||||
- React-cxxreact (= 0.69.11)
|
||||
- React-jsi (= 0.69.11)
|
||||
- React-logger (= 0.69.11)
|
||||
- React-perflogger (= 0.69.11)
|
||||
- RNCalendarEvents (2.2.0):
|
||||
- React
|
||||
- RNCAsyncStorage (1.17.3):
|
||||
@@ -706,8 +706,8 @@ SPEC CHECKSUMS:
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
CocoaLumberjack: b7e05132ff94f6ae4dfa9d5bce9141893a21d9da
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: a8af91c2b5a0029d12ff6b32e428863d63c48991
|
||||
FBReactNativeSpec: ec5e878f6452a3de5430e0b2324a4d4ae6ac63f6
|
||||
FBLazyVector: 5c0975e66853436589eae7542f4b956c7e2ef465
|
||||
FBReactNativeSpec: bb062293e84c33200005312d1807d8cb94a0d66a
|
||||
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
|
||||
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
|
||||
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
|
||||
@@ -732,19 +732,19 @@ SPEC CHECKSUMS:
|
||||
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
||||
PromisesSwift: cf9eb58666a43bbe007302226e510b16c1e10959
|
||||
RCT-Folly: b9d9fe1fc70114b751c076104e52f3b1b5e5a95a
|
||||
RCTRequired: 3581db0757e7ff9be10718a56b3d79b6a6bd3bdf
|
||||
RCTTypeSafety: ce13e630c48340401ebfb28710959913f74b8b36
|
||||
React: cca8f2b7cce018f79847ca79847fa367b206e8a1
|
||||
React-bridging: b893643f09d3964afba6c347e00dd86cf10691e5
|
||||
React-callinvoker: 9ac7cba30428eddf7a06d1253f8e7561b5c97334
|
||||
React-Codegen: 65ff9fbddf8a17a6d4f495f71d365288f934a93a
|
||||
React-Core: 550b694774bc778b5c7bf7608fc12a484e01ec05
|
||||
React-CoreModules: c332d5b416cb3ccf972e7af79d496498a700e073
|
||||
React-cxxreact: c5c4106bfd2d0cee80b848e33b7ff4e35a721b16
|
||||
React-jsi: 6ff3fb9b9764a499c959e0096c0d384fa2b4beef
|
||||
React-jsiexecutor: 388f1c99404c848141d7ea162f61233d04829ede
|
||||
React-jsinspector: a4463b3411b8b9b37153255ef694a84c77ba3c7f
|
||||
React-logger: 2a0497622cbabc47fb769d97620952df14c1f814
|
||||
RCTRequired: 8e9a57dddc8f8e9e816c67c2d2537271a997137a
|
||||
RCTTypeSafety: 2b19e268e2036a2c2f6db6deb1ac03e28b1d607a
|
||||
React: f9478e6390f177ee6b67b87a3c6afea42b39523e
|
||||
React-bridging: d405ecd3ff80e1d0a4059a11063eaa9ed7a00c58
|
||||
React-callinvoker: c8ffa61f3f06f486ba6647769fc98f19e25d165a
|
||||
React-Codegen: 73acfdac1495b91ad5efdd3ab005568263c5def6
|
||||
React-Core: 7b7c75af4b73fe0ed4e5c3cdb7d79979e81148dc
|
||||
React-CoreModules: cd6e7efb38162884f08c7afa16fffaf15ff28ae4
|
||||
React-cxxreact: 51157cc600c9f436a7e623913a03b775305ef86c
|
||||
React-jsi: 3eeb345c4828d7b132fd38064a305f31b46d4ec3
|
||||
React-jsiexecutor: 5813455a4a908fb7284aa13307a9e0386e93b0bb
|
||||
React-jsinspector: 9ca5bf73ed0a195397e45fdbcd507cf7d503c428
|
||||
React-logger: 700340e325f21ba2a2d6413a61ef14268c7360aa
|
||||
react-native-background-timer: 17ea5e06803401a379ebf1f20505b793ac44d0fe
|
||||
react-native-get-random-values: 30b3f74ca34e30e2e480de48e4add2706a40ac8f
|
||||
react-native-keep-awake: afad8a51dfef9fe9655a6344771be32c8596d774
|
||||
@@ -757,18 +757,18 @@ SPEC CHECKSUMS:
|
||||
react-native-video: bb6f12a7198db53b261fefb5d609dc77417acc8b
|
||||
react-native-webrtc: 4d1669c2ed29767fe70b0169428b4466589ecf8b
|
||||
react-native-webview: ea4899a1056c782afa96dd082179a66cbebf5504
|
||||
React-perflogger: bc57c4a953c1ec913b0d984cf4f2b9842a12bde0
|
||||
React-RCTActionSheet: 3efa3546119a1050f6c34a461b386dd9e36eaf0b
|
||||
React-RCTAnimation: e58fb9f1adf7b38af329881ea2740f43ffeea854
|
||||
React-RCTBlob: d2238645553c3ec787324268c0676148d86e6cc4
|
||||
React-RCTImage: e6d7c9ab978cae99364fcc96b9238fc7740a13da
|
||||
React-RCTLinking: 329e88ce217dad464ef34b5d0c40b3ceaac6c9ec
|
||||
React-RCTNetwork: c8967f2382aac31761ddb750fee53fa34cf7a4ee
|
||||
React-RCTSettings: 8a825b4b5ea58f6713a7c97eea6cc82e9895188b
|
||||
React-RCTText: ffcaac5c66bc065f2ccf79b6fe34585adb9e589b
|
||||
React-RCTVibration: 0039c986626b78242401931bb23c803935fae9d1
|
||||
React-runtimeexecutor: 5ebf1ddaa706bf2986123f22d2cad905443c2c5f
|
||||
ReactCommon: 65754b8932ea80272714988268bbfb9f303264a5
|
||||
React-perflogger: fdee2a0c512167ae4c19c4e230ccf6aa66a6aff0
|
||||
React-RCTActionSheet: 1cf5fef4e372f1c877969710a51bea4bb25e78fe
|
||||
React-RCTAnimation: 73816e3acd1f5e3f00166fc7eedb34f6b112f734
|
||||
React-RCTBlob: 6976c838fb14a1daf75d7c8bb23bae9cbbf726bb
|
||||
React-RCTImage: ab8a7498f215117f32271698591e4bd932dcf812
|
||||
React-RCTLinking: e8e78aed2744ab9946cc8ba5716b4938c2efb1e0
|
||||
React-RCTNetwork: 796f5aed4d932655d292bdc6b40f9502dcdb9542
|
||||
React-RCTSettings: 7e1cd2a384b45c90caf67464572abe3833b9da3b
|
||||
React-RCTText: fd6162890828f0761e03c59058fa23c3a21b2e10
|
||||
React-RCTVibration: 302cfd5cc33669d7abdb7ec6790123baba66e62e
|
||||
React-runtimeexecutor: 59407514818b2afbb1d7507e4e1ac834d24b0fbd
|
||||
ReactCommon: b8487da74723562d7368dab27135fd182f00a91c
|
||||
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
|
||||
RNCAsyncStorage: 005c0e2f09575360f142d0d1f1f15e4ec575b1af
|
||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||
@@ -780,8 +780,8 @@ SPEC CHECKSUMS:
|
||||
RNSound: 27e8268bdb0a1f191f219a33267f7e0445e8d62f
|
||||
RNSVG: f3b60aeeaa81960e2e0536c3a9eef50b667ef3a9
|
||||
RNWatch: dae6c858a2051dbdcfb00b9a86cf4d90400263b4
|
||||
Yoga: d24d6184b6b85f742536bd93bd07d69d7b9bb4c1
|
||||
Yoga: 7f5ad94937ba3fc58c151ad1b7bbada2c275b28e
|
||||
|
||||
PODFILE CHECKSUM: e3579df5272b8b697c9fdc0e55aa0845b189c4dd
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
COCOAPODS: 1.12.1
|
||||
|
||||
@@ -370,8 +370,6 @@
|
||||
"permissionCameraRequiredError": "Camera permission is required to participate in conferences with video. Please grant it in Settings",
|
||||
"permissionErrorTitle": "Permission required",
|
||||
"permissionMicRequiredError": "Microphone permission is required to participate in conferences with audio. Please grant it in Settings",
|
||||
"popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
|
||||
"popupErrorTitle": "Pop-up blocked",
|
||||
"readMore": "more",
|
||||
"recentlyUsedObjects": "Your recently used objects",
|
||||
"recording": "Recording",
|
||||
@@ -439,6 +437,7 @@
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Sorry, you're not allowed to join this call.",
|
||||
"tokenAuthFailedTitle": "Authentication failed",
|
||||
"tokenAuthUnsupported": "Token URL is not supported.",
|
||||
"transcribing": "Transcribing",
|
||||
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
|
||||
"user": "User",
|
||||
@@ -674,6 +673,8 @@
|
||||
"sessionToken": "Session Token",
|
||||
"start": "Start Recording",
|
||||
"stop": "Stop Recording",
|
||||
"stopping": "Stopping Recording",
|
||||
"wait": "Please wait while we save your recording",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"lockRoomPassword": "password",
|
||||
|
||||
@@ -6,6 +6,9 @@ const UI = {};
|
||||
import Logger from '@jitsi/logger';
|
||||
import EventEmitter from 'events';
|
||||
|
||||
import {
|
||||
conferenceWillInit
|
||||
} from '../../react/features/base/conference/actions';
|
||||
import { isMobileBrowser } from '../../react/features/base/environment/utils';
|
||||
import { setColorAlpha } from '../../react/features/base/util/helpers';
|
||||
import { setDocumentUrl } from '../../react/features/etherpad/actions';
|
||||
@@ -24,14 +27,11 @@ import {
|
||||
import UIEvents from '../../service/UI/UIEvents';
|
||||
|
||||
import EtherpadManager from './etherpad/Etherpad';
|
||||
import messageHandler from './util/MessageHandler';
|
||||
import UIUtil from './util/UIUtil';
|
||||
import VideoLayout from './videolayout/VideoLayout';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
UI.messageHandler = messageHandler;
|
||||
|
||||
const eventEmitter = new EventEmitter();
|
||||
|
||||
UI.eventEmitter = eventEmitter;
|
||||
@@ -58,30 +58,6 @@ UI.isFullScreen = function() {
|
||||
return UIUtil.isFullScreen();
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that server has shut down.
|
||||
*/
|
||||
UI.notifyGracefulShutdown = function() {
|
||||
messageHandler.showError({
|
||||
descriptionKey: 'dialog.gracefulShutdown',
|
||||
titleKey: 'dialog.serviceUnavailable'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that reservation error happened.
|
||||
*/
|
||||
UI.notifyReservationError = function(code, msg) {
|
||||
messageHandler.showError({
|
||||
descriptionArguments: {
|
||||
code,
|
||||
msg
|
||||
},
|
||||
descriptionKey: 'dialog.reservationErrorMsg',
|
||||
titleKey: 'dialog.reservationError'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize conference UI.
|
||||
*/
|
||||
@@ -91,18 +67,9 @@ UI.initConference = function() {
|
||||
|
||||
/**
|
||||
* Starts the UI module and initializes all related components.
|
||||
*
|
||||
* @returns {boolean} true if the UI is ready and the conference should be
|
||||
* established, false - otherwise (for example in the case of welcome page)
|
||||
*/
|
||||
UI.start = function() {
|
||||
VideoLayout.initLargeVideo();
|
||||
|
||||
// Do not animate the video area on UI start (second argument passed into
|
||||
// resizeVideoArea) because the animation is not visible anyway. Plus with
|
||||
// the current dom layout, the quality label is part of the video layout and
|
||||
// will be seen animating in.
|
||||
VideoLayout.resizeVideoArea();
|
||||
APP.store.dispatch(conferenceWillInit());
|
||||
|
||||
if (isMobileBrowser()) {
|
||||
document.body.classList.add('mobile-browser');
|
||||
@@ -292,40 +259,6 @@ UI.showToolbar = timeout => APP.store.dispatch(showToolbox(timeout));
|
||||
// Used by torture.
|
||||
UI.dockToolbar = dock => APP.store.dispatch(dockToolbox(dock));
|
||||
|
||||
/**
|
||||
* Notify user that connection failed.
|
||||
* @param {string} stropheErrorMsg raw Strophe error message
|
||||
*/
|
||||
UI.notifyConnectionFailed = function(stropheErrorMsg) {
|
||||
let descriptionKey;
|
||||
let descriptionArguments;
|
||||
|
||||
if (stropheErrorMsg) {
|
||||
descriptionKey = 'dialog.connectErrorWithMsg';
|
||||
descriptionArguments = { msg: stropheErrorMsg };
|
||||
} else {
|
||||
descriptionKey = 'dialog.connectError';
|
||||
}
|
||||
|
||||
messageHandler.showError({
|
||||
descriptionArguments,
|
||||
descriptionKey,
|
||||
titleKey: 'connection.CONNFAIL'
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Notify user that maximum users limit has been reached.
|
||||
*/
|
||||
UI.notifyMaxUsersLimitReached = function() {
|
||||
messageHandler.showError({
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.maxUsersLimitReached',
|
||||
titleKey: 'dialog.maxUsersLimitReachedTitle'
|
||||
});
|
||||
};
|
||||
|
||||
UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
|
||||
VideoLayout.onLastNEndpointsChanged(leavingIds, enteringIds);
|
||||
};
|
||||
@@ -337,13 +270,6 @@ UI.handleLastNEndpoints = function(leavingIds, enteringIds) {
|
||||
*/
|
||||
UI.setAudioLevel = (id, lvl) => VideoLayout.setAudioLevel(id, lvl);
|
||||
|
||||
UI.notifyTokenAuthFailed = function() {
|
||||
messageHandler.showError({
|
||||
descriptionKey: 'dialog.tokenAuthFailed',
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update list of available physical devices.
|
||||
*/
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
/* global APP */
|
||||
import Logger from '@jitsi/logger';
|
||||
|
||||
import { openConnection } from '../../../connection';
|
||||
import {
|
||||
openAuthDialog,
|
||||
openLoginDialog } from '../../../react/features/authentication/actions.web';
|
||||
import {
|
||||
LoginDialog,
|
||||
WaitForOwnerDialog
|
||||
} from '../../../react/features/authentication/components';
|
||||
import {
|
||||
getTokenAuthUrl,
|
||||
isTokenAuthEnabled
|
||||
} from '../../../react/features/authentication/functions';
|
||||
import { getReplaceParticipant } from '../../../react/features/base/config/functions';
|
||||
import { isDialogOpen } from '../../../react/features/base/dialog/functions';
|
||||
import { setJWT } from '../../../react/features/base/jwt/actions';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import ExternalLoginDialog from './LoginDialog';
|
||||
|
||||
|
||||
let externalAuthWindow;
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
|
||||
/**
|
||||
* Authenticate using external service or just focus
|
||||
* external auth window if there is one already.
|
||||
*
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function doExternalAuth(room, lockPassword) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (externalAuthWindow) {
|
||||
externalAuthWindow.focus();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (room.isJoined()) {
|
||||
let getUrl;
|
||||
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
getUrl = Promise.resolve(getTokenAuthUrl(config)(room.getName(), true));
|
||||
initJWTTokenListener(room);
|
||||
} else {
|
||||
getUrl = room.getExternalAuthUrl(true);
|
||||
}
|
||||
getUrl.then(url => {
|
||||
externalAuthWindow = ExternalLoginDialog.showExternalAuthDialog(
|
||||
url,
|
||||
() => {
|
||||
externalAuthWindow = null;
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
room.join(lockPassword);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (isTokenAuthEnabled(config)) {
|
||||
redirectToTokenAuthService(room.getName());
|
||||
} else {
|
||||
room.getExternalAuthUrl().then(UIUtil.redirect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user to the token authentication service for the login to be
|
||||
* performed. Once complete it is expected that the service will bring the user
|
||||
* back with "?jwt={the JWT token}" query parameter added.
|
||||
* @param {string} [roomName] the name of the conference room.
|
||||
*/
|
||||
export function redirectToTokenAuthService(roomName) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
// FIXME: This method will not preserve the other URL params that were
|
||||
// originally passed.
|
||||
UIUtil.redirect(getTokenAuthUrl(config)(roomName, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes 'message' listener that will wait for a JWT token to be received
|
||||
* from the token authentication service opened in a popup window.
|
||||
* @param room the name of the conference room.
|
||||
*/
|
||||
function initJWTTokenListener(room) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function listener({ data, source }) {
|
||||
if (externalAuthWindow !== source) {
|
||||
logger.warn('Ignored message not coming '
|
||||
+ 'from external authnetication window');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let jwt;
|
||||
|
||||
if (data && (jwt = data.jwtToken)) {
|
||||
logger.info('Received JSON Web Token (JWT):', jwt);
|
||||
|
||||
APP.store.dispatch(setJWT(jwt));
|
||||
|
||||
const roomName = room.getName();
|
||||
|
||||
openConnection({
|
||||
retry: false,
|
||||
roomName
|
||||
}).then(connection => {
|
||||
// Start new connection
|
||||
const newRoom = connection.initJitsiConference(
|
||||
roomName, APP.conference._getConferenceOptions());
|
||||
|
||||
// Authenticate from the new connection to get
|
||||
// the session-ID from the focus, which will then be used
|
||||
// to upgrade current connection's user role
|
||||
|
||||
newRoom.room.moderator.authenticate()
|
||||
.then(() => {
|
||||
connection.disconnect();
|
||||
|
||||
// At this point we'll have session-ID stored in
|
||||
// the settings. It will be used in the call below
|
||||
// to upgrade user's role
|
||||
room.room.moderator.authenticate()
|
||||
.then(() => {
|
||||
logger.info('User role upgrade done !');
|
||||
// eslint-disable-line no-use-before-define
|
||||
unregister();
|
||||
})
|
||||
.catch((err, errCode) => {
|
||||
logger.error('Authentication failed: ',
|
||||
err, errCode);
|
||||
unregister();
|
||||
});
|
||||
})
|
||||
.catch((error, code) => {
|
||||
unregister();
|
||||
connection.disconnect();
|
||||
logger.error(
|
||||
'Authentication failed on the new connection',
|
||||
error, code);
|
||||
});
|
||||
}, err => {
|
||||
unregister();
|
||||
logger.error('Failed to open new connection', err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function unregister() {
|
||||
window.removeEventListener('message', listener);
|
||||
}
|
||||
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('message', listener, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate for the conference.
|
||||
* Uses external service for auth if conference supports that.
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function authenticate(room, lockPassword) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
|
||||
doExternalAuth(room, lockPassword);
|
||||
} else {
|
||||
APP.store.dispatch(openLoginDialog());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify user that authentication is required to create the conference.
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function requireAuth(room, lockPassword) {
|
||||
if (isDialogOpen(APP.store, WaitForOwnerDialog) || isDialogOpen(APP.store, LoginDialog)) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(
|
||||
openAuthDialog(
|
||||
room.getName(), authenticate.bind(null, room, lockPassword))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* De-authenticate local user.
|
||||
*
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function logout(room) {
|
||||
return new Promise(resolve => {
|
||||
room.room.moderator.logout(resolve);
|
||||
}).then(url => {
|
||||
// de-authenticate conference on the fly
|
||||
if (room.isJoined()) {
|
||||
const replaceParticipant = getReplaceParticipant(APP.store.getState());
|
||||
|
||||
room.join(null, replaceParticipant);
|
||||
}
|
||||
|
||||
return url;
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
logout,
|
||||
requireAuth
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
/* global APP */
|
||||
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Show notification that external auth is required (using provided url).
|
||||
* @param {string} url - URL to use for external auth.
|
||||
* @param {function} callback - callback to invoke when auth popup is closed.
|
||||
* @returns auth dialog
|
||||
*/
|
||||
showExternalAuthDialog(url, callback) {
|
||||
const dialog = APP.UI.messageHandler.openCenteredPopup(
|
||||
url, 910, 660,
|
||||
|
||||
// On closed
|
||||
callback
|
||||
);
|
||||
|
||||
if (!dialog) {
|
||||
APP.UI.messageHandler.showWarning({
|
||||
descriptionKey: 'dialog.popupError',
|
||||
titleKey: 'dialog.popupErrorTitle'
|
||||
});
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
};
|
||||
@@ -1,61 +0,0 @@
|
||||
/* global APP */
|
||||
|
||||
import { showErrorNotification, showWarningNotification } from '../../../react/features/notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../react/features/notifications/constants';
|
||||
|
||||
const messageHandler = {
|
||||
/**
|
||||
* Opens new popup window for given <tt>url</tt> centered over current
|
||||
* window.
|
||||
*
|
||||
* @param url the URL to be displayed in the popup window
|
||||
* @param w the width of the popup window
|
||||
* @param h the height of the popup window
|
||||
* @param onPopupClosed optional callback function called when popup window
|
||||
* has been closed.
|
||||
*
|
||||
* @returns {object} popup window object if opened successfully or undefined
|
||||
* in case we failed to open it(popup blocked)
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
openCenteredPopup(url, w, h, onPopupClosed) {
|
||||
const l = window.screenX + (window.innerWidth / 2) - (w / 2);
|
||||
const t = window.screenY + (window.innerHeight / 2) - (h / 2);
|
||||
const popup = window.open(
|
||||
url, '_blank',
|
||||
String(`top=${t}, left=${l}, width=${w}, height=${h}`));
|
||||
|
||||
if (popup && onPopupClosed) {
|
||||
const pollTimer = window.setInterval(() => {
|
||||
if (popup.closed !== false) {
|
||||
window.clearInterval(pollTimer);
|
||||
onPopupClosed();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
return popup;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows an error dialog to the user.
|
||||
*
|
||||
* @param {object} props - The properties to pass to the
|
||||
* showErrorNotification action.
|
||||
*/
|
||||
showError(props) {
|
||||
APP.store.dispatch(showErrorNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a warning dialog to the user.
|
||||
*
|
||||
* @param {object} props - The properties to pass to the
|
||||
* showWarningNotification action.
|
||||
*/
|
||||
showWarning(props) {
|
||||
APP.store.dispatch(showWarningNotification(props, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
}
|
||||
};
|
||||
|
||||
export default messageHandler;
|
||||
@@ -31,18 +31,6 @@ const UIUtil = {
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirects to a given URL.
|
||||
*
|
||||
* @param {string} url - The redirect URL.
|
||||
* NOTE: Currently used to redirect to 3rd party location for
|
||||
* authentication. In most cases redirectWithStoredParams action must be
|
||||
* used instead of this method in order to preserve current URL params.
|
||||
*/
|
||||
redirect(url) {
|
||||
window.location.href = url;
|
||||
},
|
||||
|
||||
/**
|
||||
* Indicates if we're currently in full screen mode.
|
||||
*
|
||||
|
||||
82
package-lock.json
generated
82
package-lock.json
generated
@@ -60,7 +60,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/v1654.0.0+782350e0/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -74,7 +74,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.69.10",
|
||||
"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",
|
||||
@@ -102,7 +102,7 @@
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-window": "1.8.6",
|
||||
"react-youtube": "10.1.0",
|
||||
@@ -5661,7 +5661,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
@@ -5842,7 +5841,6 @@
|
||||
"version": "7.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
|
||||
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
@@ -12762,8 +12760,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1654.0.0+782350e0/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-uGRFpvKj6FSDhVHWvMerNwzaLflUrHQLXiP7Py0ITqV5Ix0d+hTFw9+OgI/JXzdBM3SLOP3GVNQLQfNR45oveA==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -15667,9 +15665,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native": {
|
||||
"version": "0.69.10",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.10.tgz",
|
||||
"integrity": "sha512-zBsJmFsjyx9zC0JDRH6F6hib3BWbIu65EYPsJBQMDHMSGJ9fL7C6ZV49O7Abockn/dbCHhIWpiSyCuda/bhV8g==",
|
||||
"version": "0.69.11",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.11.tgz",
|
||||
"integrity": "sha512-lYSzyDicrE5FLag+Vaq1XmPscQFKFqiUCsDMNkgd+wW9139u3hDEnbEWnXfM/kgF3vrKQUfyLMwfkuLV8EQfug==",
|
||||
"deprecated": "Issues and pull requests filed against this version are not supported. See the React Native release support policy to learn more: https://github.com/reactwg/react-native-releases#releases-support-policy",
|
||||
"dependencies": {
|
||||
"@jest/create-cache-key-function": "^27.0.1",
|
||||
"@react-native-community/cli": "^8.0.4",
|
||||
@@ -16123,27 +16122,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.0.tgz",
|
||||
"integrity": "sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==",
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.4.5",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"invariant": "^2.2.4",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.6"
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3",
|
||||
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
|
||||
"react": "^16.8.3 || ^17 || ^18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz",
|
||||
@@ -23851,7 +23852,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
@@ -24032,7 +24032,6 @@
|
||||
"version": "7.1.24",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz",
|
||||
"integrity": "sha512-7FkurKcS1k0FHZEtdbbgN8Oc6b+stGSfZYjQGicofJ0j4U0qIn/jaSvnP2pLwZKiai3/17xqqxkkrxTgN8UNbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
@@ -29269,8 +29268,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1654.0.0+782350e0/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-uGRFpvKj6FSDhVHWvMerNwzaLflUrHQLXiP7Py0ITqV5Ix0d+hTFw9+OgI/JXzdBM3SLOP3GVNQLQfNR45oveA==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -31460,9 +31459,9 @@
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.69.10",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.10.tgz",
|
||||
"integrity": "sha512-zBsJmFsjyx9zC0JDRH6F6hib3BWbIu65EYPsJBQMDHMSGJ9fL7C6ZV49O7Abockn/dbCHhIWpiSyCuda/bhV8g==",
|
||||
"version": "0.69.11",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.11.tgz",
|
||||
"integrity": "sha512-lYSzyDicrE5FLag+Vaq1XmPscQFKFqiUCsDMNkgd+wW9139u3hDEnbEWnXfM/kgF3vrKQUfyLMwfkuLV8EQfug==",
|
||||
"requires": {
|
||||
"@jest/create-cache-key-function": "^27.0.1",
|
||||
"@react-native-community/cli": "^8.0.4",
|
||||
@@ -31776,23 +31775,16 @@
|
||||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.0.tgz",
|
||||
"integrity": "sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==",
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.5",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"invariant": "^2.2.4",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
}
|
||||
"react-is": "^17.0.2"
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
|
||||
10
package.json
10
package.json
@@ -65,7 +65,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/v1654.0.0+782350e0/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -79,7 +79,7 @@
|
||||
"react-focus-on": "3.8.1",
|
||||
"react-i18next": "10.11.4",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
"react-native": "0.69.10",
|
||||
"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",
|
||||
@@ -107,7 +107,7 @@
|
||||
"react-native-webrtc": "111.0.3",
|
||||
"react-native-webview": "11.15.1",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"react-textarea-autosize": "8.3.0",
|
||||
"react-window": "1.8.6",
|
||||
"react-youtube": "10.1.0",
|
||||
@@ -179,9 +179,7 @@
|
||||
"webpack-dev-server": "4.7.3"
|
||||
},
|
||||
"overrides": {
|
||||
"strophe.js@1.5.0": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
}
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0",
|
||||
|
||||
19
patches/react-native-immersive+2.0.0.patch
Normal file
19
patches/react-native-immersive+2.0.0.patch
Normal file
@@ -0,0 +1,19 @@
|
||||
diff --git a/node_modules/react-native-immersive/index.js b/node_modules/react-native-immersive/index.js
|
||||
index 55dab57..110260b 100644
|
||||
--- a/node_modules/react-native-immersive/index.js
|
||||
+++ b/node_modules/react-native-immersive/index.js
|
||||
@@ -18,7 +18,13 @@ const Immersive = Platform.OS === 'android' ? {
|
||||
isListenerEnabled = true
|
||||
RNImmersive.addImmersiveListener()
|
||||
},
|
||||
- removeImmersiveListener: (listener) => DeviceEventEmitter.removeListener('@@IMMERSIVE_STATE_CHANGED', listener)
|
||||
+ removeImmersiveListener: (listener) => {
|
||||
+ const immersiveListener = DeviceEventEmitter.addListener('@@IMMERSIVE_STATE_CHANGED', listener);
|
||||
+
|
||||
+ return () => {
|
||||
+ immersiveListener.remove();
|
||||
+ }
|
||||
+ }
|
||||
} : {
|
||||
on: unSupportedError,
|
||||
off: unSupportedError,
|
||||
1
react-native-sdk/.npmignore
Normal file
1
react-native-sdk/.npmignore
Normal file
@@ -0,0 +1 @@
|
||||
*.tgz
|
||||
@@ -6,7 +6,7 @@ Inside your project, run `npm i @jitsi/react-native-sdk`.<br/><br/>Additionally,
|
||||
|
||||
This can be done by running the following script:
|
||||
```
|
||||
"update-deps": "node node_modules/@jitsi/react-native-sdk/update_dependencies.js"
|
||||
node node_modules/@jitsi/react-native-sdk/update_dependencies.js
|
||||
```
|
||||
This will check and update all your dependencies.<br/><br/>
|
||||
|
||||
@@ -63,24 +63,15 @@ module.exports = (async () => {
|
||||
SOUNDS_DIR="${PROJECT_DIR}/../node_modules/@jitsi/react-native-sdk/sounds"
|
||||
cp $SOUNDS_DIR/* ${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/
|
||||
```
|
||||
#### Podfile
|
||||
- At the beginning of your target step add `pod 'ObjectiveDropboxOfficial', :modular_headers => true`
|
||||
|
||||
Run `cd ios && pod install && cd ..`
|
||||
|
||||
### Android
|
||||
|
||||
- In your build.gradle have at least `minSdkVersion = 24`
|
||||
- TODO: HOW TO ADD COPY SOUNDS STEP
|
||||
- In `android/app/src/debug/AndroidManifest.xml` and `android/app/src/main/AndroidManifest.xml`, under the `</application>` tag, include
|
||||
```
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
```
|
||||
|
||||
### TODOS
|
||||
- Ref ConnectionService to not rely on ReactInstanceHolder anymore
|
||||
- Add Copy Sounds step to build.gradle
|
||||
- Include copy sounds step in podspec (if possible)
|
||||
- Add ranges for dependencies
|
||||
- Add Build_Config for react native to AppInfoModule
|
||||
|
||||
@@ -16,11 +16,10 @@ Pod::Spec.new do |s|
|
||||
s.platform = :ios, '12.4'
|
||||
|
||||
s.preserve_paths = 'ios/**/*'
|
||||
s.source_files = 'ios/**/*.{h,m,swift}'
|
||||
|
||||
s.source_files = 'ios/**/*.{h,m}'
|
||||
|
||||
s.dependency 'React-Core'
|
||||
s.dependency 'ObjectiveDropboxOfficial', '6.2.3'
|
||||
s.dependency 'JitsiWebRTC', '~> 111.0.0'
|
||||
s.dependency 'react-native-webrtc'
|
||||
|
||||
s.dependency 'ObjectiveDropboxOfficial', '6.2.3'
|
||||
end
|
||||
|
||||
153
react-native-sdk/package-lock.json
generated
153
react-native-sdk/package-lock.json
generated
@@ -28,7 +28,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/v1652.0.0+90da4884/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -44,7 +44,7 @@
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.4.1",
|
||||
"unorm": "1.6.0",
|
||||
@@ -831,11 +831,51 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.15.tgz",
|
||||
"integrity": "sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
|
||||
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.7",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.7.tgz",
|
||||
@@ -1300,6 +1340,11 @@
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||
},
|
||||
"node_modules/current-executing-script": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/current-executing-script/-/current-executing-script-0.1.3.tgz",
|
||||
@@ -2398,8 +2443,8 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1652.0.0+90da4884/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-QLXzAvGwQLpqpQmnbW9wOeScEf3WoBH+osLywzil6FAMsf3URoHR4jFLmkSUAvoxsLbpUSI1ziFFIprbYPnWuQ==",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -3023,22 +3068,34 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.0.tgz",
|
||||
"integrity": "sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==",
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.4.5",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"invariant": "^2.2.4",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.6"
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3",
|
||||
"redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0"
|
||||
"react": "^16.8.3 || ^17 || ^18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux/node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
@@ -4148,11 +4205,51 @@
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.15.tgz",
|
||||
"integrity": "sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA==",
|
||||
"requires": {
|
||||
"@types/prop-types": "*",
|
||||
"@types/scheduler": "*",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
|
||||
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
|
||||
},
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.8.7",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.7.tgz",
|
||||
@@ -4480,6 +4577,11 @@
|
||||
"css-tree": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
|
||||
},
|
||||
"current-executing-script": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/current-executing-script/-/current-executing-script-0.1.3.tgz",
|
||||
@@ -5247,8 +5349,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1652.0.0+90da4884/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-QLXzAvGwQLpqpQmnbW9wOeScEf3WoBH+osLywzil6FAMsf3URoHR4jFLmkSUAvoxsLbpUSI1ziFFIprbYPnWuQ==",
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-kTBN3NlI3RLxeQeq5uppU557kJPQWwFMq1NweGr0CH8TCJdnKt30Lqx/X+DGP7Js6286JDmpg4EWZa9wW7lLXQ==",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
@@ -5705,16 +5807,23 @@
|
||||
}
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.0.tgz",
|
||||
"integrity": "sha512-hyu/PoFK3vZgdLTg9ozbt7WF3GgX5+Yn3pZm5/96/o4UueXA+zj08aiSC9Mfj2WtD1bvpIb3C5yvskzZySzzaw==",
|
||||
"version": "7.2.9",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz",
|
||||
"integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.4.5",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"invariant": "^2.2.4",
|
||||
"@babel/runtime": "^7.15.4",
|
||||
"@types/react-redux": "^7.1.20",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.8.6"
|
||||
"react-is": "^17.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
|
||||
@@ -29,7 +29,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/v1652.0.0+90da4884/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1659.0.0+5d322ea5/lib-jitsi-meet.tgz",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -45,7 +45,7 @@
|
||||
"react-native-tab-view": "3.1.1",
|
||||
"react-native-url-polyfill": "1.3.0",
|
||||
"react-native-youtube-iframe": "2.2.1",
|
||||
"react-redux": "7.1.0",
|
||||
"react-redux": "7.2.9",
|
||||
"redux": "4.0.4",
|
||||
"redux-thunk": "2.4.1",
|
||||
"unorm": "1.6.0",
|
||||
@@ -86,9 +86,7 @@
|
||||
"react-native-webview": "11.15.1"
|
||||
},
|
||||
"overrides": {
|
||||
"strophe.js@1.5.0": {
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
}
|
||||
"@xmldom/xmldom": "0.8.7"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node sdk_instructions.js",
|
||||
|
||||
4
react-native-sdk/prepare_sdk.js
vendored
4
react-native-sdk/prepare_sdk.js
vendored
@@ -107,10 +107,6 @@ copyFolderRecursiveSync(
|
||||
`${iosSrcPath}/dropbox`,
|
||||
iosDestPath
|
||||
);
|
||||
copyFolderRecursiveSync(
|
||||
'../ios/sdk/src/picture-in-picture',
|
||||
iosDestPath
|
||||
);
|
||||
fs.copyFileSync(
|
||||
`${iosSrcPath}/AppInfo.m`,
|
||||
`${iosDestPath}/AppInfo.m`
|
||||
|
||||
22
react-native-sdk/update_dependencies.js
vendored
22
react-native-sdk/update_dependencies.js
vendored
@@ -23,18 +23,24 @@ function updateDependencies() {
|
||||
}
|
||||
}
|
||||
|
||||
packageJSON.overrides = packageJSON.overrides || {};
|
||||
|
||||
for (const key in RNSDKpackageJSON.overrides) {
|
||||
if (!packageJSON.overrides.hasOwnProperty(key)) {
|
||||
packageJSON.overrides[key] = RNSDKpackageJSON.overrides[key];
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`
|
||||
=========================
|
||||
The following dependencies were added to your package.json:
|
||||
\n
|
||||
${Object.keys(packageJSON.dependencies)}
|
||||
\n
|
||||
Make sure you run npm install
|
||||
If you are building for ios run cd ios && pod install to link them.
|
||||
🚀 Your project was updated!
|
||||
🛠 Make sure you run npm install
|
||||
📱 If you are building for iOS run cd ios && pod install to link them.
|
||||
=========================
|
||||
`);
|
||||
|
||||
@@ -46,9 +52,7 @@ If you are building for ios run cd ios && pod install to link them.
|
||||
return item;
|
||||
}, {});
|
||||
|
||||
const data = JSON.stringify(packageJSON, null, 2);
|
||||
|
||||
fs.writeFileSync(pathToPackageJSON, data);
|
||||
fs.writeFileSync(pathToPackageJSON, JSON.stringify(packageJSON, null, 2));
|
||||
|
||||
console.log(
|
||||
'All needed dependencies have been updated. \nPlease run npm install.'
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
import {
|
||||
createFakeConfig,
|
||||
restoreConfig
|
||||
} from '../base/config/functions';
|
||||
import { connect, disconnect, setLocationURL } from '../base/connection/actions';
|
||||
} from '../base/config/functions.native';
|
||||
import { connect, disconnect, setLocationURL } from '../base/connection/actions.native';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet/functions.native';
|
||||
import { createDesiredLocalTracks } from '../base/tracks/actions';
|
||||
import { createDesiredLocalTracks } from '../base/tracks/actions.native';
|
||||
import isInsecureRoomName from '../base/util/isInsecureRoomName';
|
||||
import { parseURLParams } from '../base/util/parseURLParams';
|
||||
import {
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from '../mobile/navigation/rootNavigationContainerRef';
|
||||
import { screen } from '../mobile/navigation/routes';
|
||||
import { clearNotifications } from '../notifications/actions';
|
||||
import { isUnsafeRoomWarningEnabled } from '../prejoin/functions';
|
||||
|
||||
import { addTrackStateToURL, getDefaultURL } from './functions.native';
|
||||
import logger from './logger';
|
||||
@@ -137,7 +138,7 @@ export function appNavigate(uri?: string, options: IReloadNowOptions = {}) {
|
||||
dispatch(setRoom(room));
|
||||
|
||||
if (room) {
|
||||
if (isInsecureRoomName(room)) {
|
||||
if (isUnsafeRoomWarningEnabled(getState()) && isInsecureRoomName(room)) {
|
||||
navigateRoot(screen.unsafeRoomWarning);
|
||||
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../analytics/middleware';
|
||||
import '../authentication/middleware';
|
||||
import '../av-moderation/middleware';
|
||||
import '../base/conference/middleware';
|
||||
import '../base/config/middleware';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import '../authentication/middleware';
|
||||
import '../dynamic-branding/middleware';
|
||||
import '../gifs/middleware';
|
||||
import '../mobile/audio-mode/middleware';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../authentication/middleware';
|
||||
import '../base/connection/middleware';
|
||||
import '../base/i18n/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../base/media/middleware';
|
||||
|
||||
@@ -8,6 +8,24 @@
|
||||
*/
|
||||
export const CANCEL_LOGIN = 'CANCEL_LOGIN';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals to login.
|
||||
*
|
||||
* {
|
||||
* type: LOGOUT
|
||||
* }
|
||||
*/
|
||||
export const LOGIN = 'LOGIN';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals to logout.
|
||||
*
|
||||
* {
|
||||
* type: LOGOUT
|
||||
* }
|
||||
*/
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that the cyclic operation of waiting
|
||||
* for conference owner has been aborted.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { checkIfCanJoin } from '../base/conference/actions';
|
||||
import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
|
||||
import {
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
@@ -126,6 +126,16 @@ function _upgradeRoleStarted(thenableWithCancel: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides an authentication dialog where the local participant
|
||||
* should authenticate.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function hideLoginDialog() {
|
||||
return hideDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens {@link WaitForOnwerDialog}.
|
||||
*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { appNavigate } from '../app/actions';
|
||||
import { appNavigate } from '../app/actions.native';
|
||||
import { IStore } from '../app/types';
|
||||
import { conferenceLeft } from '../base/conference/actions';
|
||||
import { connectionFailed } from '../base/connection/actions.native';
|
||||
@@ -61,4 +61,11 @@ export function cancelWaitForOwner() {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/** .
|
||||
* Redirect to the default location (e.g. Welcome page).
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function redirectToDefaultLocation() {
|
||||
return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions.web';
|
||||
import { IStore } from '../app/types';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN
|
||||
CANCEL_LOGIN,
|
||||
LOGIN,
|
||||
LOGOUT
|
||||
} from './actionTypes';
|
||||
import LoginDialog from './components/web/LoginDialog';
|
||||
import WaitForOwnerDialog from './components/web/WaitForOwnerDialog';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -35,31 +34,37 @@ export function cancelWaitForOwner() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a authentication dialog where the local participant
|
||||
* should authenticate.
|
||||
/** .
|
||||
* Redirect to the default location (e.g. Welcome page).
|
||||
*
|
||||
* @returns {Function}.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function hideLoginDialog() {
|
||||
return hideDialog(LoginDialog);
|
||||
export function redirectToDefaultLocation() {
|
||||
return (dispatch: IStore['dispatch']) => dispatch(maybeRedirectToWelcomePage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification dialog that authentication is required to create the.
|
||||
* Conference.
|
||||
* This is used for external auth.
|
||||
* Login.
|
||||
*
|
||||
* @param {string} room - The room name.
|
||||
* @param {Function} onAuthNow - The function to be invoked when external authentication.
|
||||
*
|
||||
* @returns {Function}.
|
||||
* @returns {{
|
||||
* type: LOGIN
|
||||
* }}
|
||||
*/
|
||||
export function openAuthDialog(room: String, onAuthNow?: Function) {
|
||||
return openDialog(WaitForOwnerDialog, {
|
||||
room,
|
||||
onAuthNow
|
||||
});
|
||||
export function login() {
|
||||
return {
|
||||
type: LOGIN
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Logout.
|
||||
*
|
||||
* @returns {{
|
||||
* type: LOGOUT
|
||||
* }}
|
||||
*/
|
||||
export function logout() {
|
||||
return {
|
||||
type: LOGOUT
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect as reduxConnect } from 'react-redux';
|
||||
|
||||
// @ts-expect-error
|
||||
import { connect } from '../../../../../connection';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { IJitsiConference } from '../../../base/conference/reducer';
|
||||
import { IConfig } from '../../../base/config/configType';
|
||||
import { connect } from '../../../base/connection/actions.web';
|
||||
import { toJid } from '../../../base/connection/functions';
|
||||
import { translate, translateToHTML } from '../../../base/i18n/functions';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
@@ -54,11 +53,6 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Invoked when username and password are submitted.
|
||||
*/
|
||||
onSuccess: Function;
|
||||
|
||||
/**
|
||||
* Conference room name.
|
||||
*/
|
||||
@@ -70,11 +64,6 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* Authentication process starts before joining the conference room.
|
||||
*/
|
||||
loginStarted: boolean;
|
||||
|
||||
/**
|
||||
* The user entered password for the conference.
|
||||
*/
|
||||
@@ -102,8 +91,7 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
password: '',
|
||||
loginStarted: false
|
||||
password: ''
|
||||
};
|
||||
|
||||
this._onCancelLogin = this._onCancelLogin.bind(this);
|
||||
@@ -135,8 +123,6 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
const {
|
||||
_conference: conference,
|
||||
_configHosts: configHosts,
|
||||
roomName,
|
||||
onSuccess,
|
||||
dispatch
|
||||
} = this.props;
|
||||
const { password, username } = this.state;
|
||||
@@ -148,19 +134,7 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
if (conference) {
|
||||
dispatch(authenticateAndUpgradeRole(jid, password, conference));
|
||||
} else {
|
||||
this.setState({
|
||||
loginStarted: true
|
||||
});
|
||||
|
||||
connect(jid, password, roomName)
|
||||
.then((connection: any) => {
|
||||
onSuccess?.(connection);
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
loginStarted: false
|
||||
});
|
||||
});
|
||||
dispatch(connect(jid, password));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +223,7 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
_connecting: connecting,
|
||||
t
|
||||
} = this.props;
|
||||
const { password, loginStarted, username } = this.state;
|
||||
const { password, username } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -258,7 +232,6 @@ class LoginDialog extends Component<IProps, IState> {
|
||||
hideCloseButton = { true }
|
||||
ok = {{
|
||||
disabled: connecting
|
||||
|| loginStarted
|
||||
|| !password
|
||||
|| !username,
|
||||
translationKey: 'dialog.login'
|
||||
@@ -315,7 +288,7 @@ function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
_conference: authRequired || conference,
|
||||
_configHosts: configHosts,
|
||||
_connecting: connecting || thenableWithCancel,
|
||||
_connecting: Boolean(connecting) || Boolean(thenableWithCancel),
|
||||
_error: connectionError || authenticateAndUpgradeRoleError,
|
||||
_progress: progress
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
|
||||
import { IStore } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import { cancelWaitForOwner } from '../../actions.web';
|
||||
import { cancelWaitForOwner, login } from '../../actions.web';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
@@ -16,11 +16,6 @@ interface IProps extends WithTranslation {
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Function to be invoked after click.
|
||||
*/
|
||||
onAuthNow?: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,9 +56,7 @@ class WaitForOwnerDialog extends PureComponent<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onIAmHost() {
|
||||
const { onAuthNow } = this.props;
|
||||
|
||||
onAuthNow?.();
|
||||
this.props.dispatch(login());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { appNavigate } from '../app/actions.native';
|
||||
import { IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
@@ -6,6 +5,7 @@ import {
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference/actionTypes';
|
||||
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 {
|
||||
@@ -13,19 +13,28 @@ import {
|
||||
JitsiConnectionErrors
|
||||
} 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 {
|
||||
CANCEL_LOGIN,
|
||||
LOGIN,
|
||||
LOGOUT,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
import {
|
||||
hideLoginDialog,
|
||||
openLoginDialog,
|
||||
openWaitForOwnerDialog,
|
||||
redirectToDefaultLocation,
|
||||
stopWaitForOwner,
|
||||
waitForOwner } from './actions.native';
|
||||
waitForOwner } from './actions';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
import { getTokenAuthUrl, isTokenAuthEnabled } from './functions';
|
||||
|
||||
/**
|
||||
* Middleware that captures connection or conference failed errors and controls
|
||||
@@ -40,7 +49,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CANCEL_LOGIN: {
|
||||
const { dispatch, getState } = store;
|
||||
const { thenableWithCancel } = getState()['features/authentication'];
|
||||
const state = getState();
|
||||
const { thenableWithCancel } = state['features/authentication'];
|
||||
|
||||
thenableWithCancel?.cancel();
|
||||
|
||||
@@ -57,10 +67,8 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Go back to the app's entry point.
|
||||
_hideLoginDialog(store);
|
||||
dispatch(hideLoginDialog());
|
||||
|
||||
const state = getState();
|
||||
const { authRequired, conference } = state['features/base/conference'];
|
||||
const { passwordRequired } = state['features/base/connection'];
|
||||
|
||||
@@ -68,7 +76,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// NOTE: Despite it's confusing name, `passwordRequired` implies an XMPP
|
||||
// connection auth error.
|
||||
if ((passwordRequired || authRequired) && !conference) {
|
||||
dispatch(appNavigate(undefined));
|
||||
dispatch(redirectToDefaultLocation());
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -100,7 +108,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
if (_isWaitingForOwner(store)) {
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
_hideLoginDialog(store);
|
||||
store.dispatch(hideLoginDialog());
|
||||
break;
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
@@ -108,18 +116,43 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
break;
|
||||
|
||||
case CONNECTION_ESTABLISHED:
|
||||
_hideLoginDialog(store);
|
||||
store.dispatch(hideLoginDialog());
|
||||
break;
|
||||
|
||||
case CONNECTION_FAILED: {
|
||||
const { error } = action;
|
||||
const state = store.getState();
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
if (error
|
||||
&& error.name === JitsiConnectionErrors.PASSWORD_REQUIRED
|
||||
&& typeof error.recoverable === 'undefined') {
|
||||
&& typeof error.recoverable === 'undefined'
|
||||
&& !jwt) {
|
||||
error.recoverable = true;
|
||||
store.dispatch(openLoginDialog());
|
||||
|
||||
_handleLogin(store);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LOGIN: {
|
||||
_handleLogin(store);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case LOGOUT: {
|
||||
const { conference } = store.getState()['features/base/conference'];
|
||||
|
||||
if (!conference) {
|
||||
break;
|
||||
}
|
||||
|
||||
store.dispatch(openLogoutDialog(() =>
|
||||
conference.room.moderator.logout(() => store.dispatch(hangup(true)))
|
||||
));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -132,7 +165,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const { error, progress } = action;
|
||||
|
||||
if (!error && progress === 1) {
|
||||
_hideLoginDialog(store);
|
||||
store.dispatch(hideLoginDialog());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -144,7 +177,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
|
||||
action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
|
||||
|
||||
// The WAIT_FOR_OWNER action is cyclic and we don't want to hide the
|
||||
// The WAIT_FOR_OWNER action is cyclic, and we don't want to hide the
|
||||
// login dialog every few seconds.
|
||||
isDialogOpen(store, LoginDialog)
|
||||
|| store.dispatch(openWaitForOwnerDialog());
|
||||
@@ -162,22 +195,12 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _clearExistingWaitForOwnerTimeout(
|
||||
{ getState }: IStore) {
|
||||
function _clearExistingWaitForOwnerTimeout({ getState }: IStore) {
|
||||
const { waitForOwnerTimeoutID } = getState()['features/authentication'];
|
||||
|
||||
waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides {@link LoginDialog} if it's currently displayed.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _hideLoginDialog({ dispatch }: IStore) {
|
||||
dispatch(hideDialog(LoginDialog));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cyclic "wait for conference owner" task is currently scheduled.
|
||||
@@ -188,3 +211,35 @@ function _hideLoginDialog({ dispatch }: IStore) {
|
||||
function _isWaitingForOwner({ getState }: IStore) {
|
||||
return Boolean(getState()['features/authentication'].waitForOwnerTimeoutID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles login challenge. Opens login dialog or redirects to token auth URL.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleLogin({ dispatch, getState }: IStore) {
|
||||
const state = getState();
|
||||
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));
|
||||
|
||||
dispatch(redirectToDefaultLocation());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: This method will not preserve the other URL params that were originally passed.
|
||||
// redirectToTokenAuthService
|
||||
window.location.href = getTokenAuthUrl(config)(room, false);
|
||||
} else {
|
||||
dispatch(openLoginDialog());
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions.web';
|
||||
import { IStore } from '../app/types';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference/actionTypes';
|
||||
import { CONNECTION_ESTABLISHED } from '../base/connection/actionTypes';
|
||||
import { hideDialog } from '../base/dialog/actions';
|
||||
import { isDialogOpen } from '../base/dialog/functions';
|
||||
import {
|
||||
JitsiConferenceErrors
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
UPGRADE_ROLE_FINISHED,
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
import {
|
||||
hideLoginDialog,
|
||||
openWaitForOwnerDialog,
|
||||
stopWaitForOwner
|
||||
} from './actions.web';
|
||||
import LoginDialog from './components/web/LoginDialog';
|
||||
import WaitForOwnerDialog from './components/web/WaitForOwnerDialog';
|
||||
|
||||
/**
|
||||
* Middleware that captures connection or conference failed errors and controls
|
||||
* {@link WaitForOwnerDialog} and {@link LoginDialog}.
|
||||
*
|
||||
* FIXME Some of the complexity was introduced by the lack of dialog stacking.
|
||||
*
|
||||
* @param {Store} store - Redux store.
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
|
||||
case CANCEL_LOGIN: {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
if (!isDialogOpen(store, WaitForOwnerDialog)) {
|
||||
if (_isWaitingForOwner(store)) {
|
||||
dispatch(openWaitForOwnerDialog());
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
dispatch(hideLoginDialog());
|
||||
|
||||
const { authRequired, conference } = getState()['features/base/conference'];
|
||||
const { passwordRequired } = getState()['features/base/connection'];
|
||||
|
||||
// Only end the meeting if we are not already inside and trying to upgrade.
|
||||
if ((authRequired && !conference) || passwordRequired) {
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_FAILED: {
|
||||
const { error } = action;
|
||||
let recoverable;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.AUTHENTICATION_REQUIRED) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
recoverable = error.recoverable;
|
||||
}
|
||||
if (recoverable) {
|
||||
// we haven't migrated all the code from AuthHandler, and we need for now conference.js to trigger
|
||||
// the dialog to pass all required parameters to WaitForOwnerDialog
|
||||
// keep it commented, so we do not trigger sending iqs to jicofo twice
|
||||
// and showing the broken dialog with no handler
|
||||
// store.dispatch(waitForOwner());
|
||||
} else {
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
store.dispatch(stopWaitForOwner());
|
||||
store.dispatch(hideLoginDialog());
|
||||
break;
|
||||
|
||||
case CONFERENCE_LEFT:
|
||||
store.dispatch(stopWaitForOwner());
|
||||
break;
|
||||
|
||||
case CONNECTION_ESTABLISHED:
|
||||
store.dispatch(hideLoginDialog());
|
||||
break;
|
||||
|
||||
case STOP_WAIT_FOR_OWNER:
|
||||
_clearExistingWaitForOwnerTimeout(store);
|
||||
store.dispatch(hideDialog(WaitForOwnerDialog));
|
||||
break;
|
||||
|
||||
case UPGRADE_ROLE_FINISHED: {
|
||||
const { error, progress } = action;
|
||||
|
||||
if (!error && progress === 1) {
|
||||
store.dispatch(hideLoginDialog());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case WAIT_FOR_OWNER: {
|
||||
_clearExistingWaitForOwnerTimeout(store);
|
||||
|
||||
const { handler, timeoutMs }: { handler: () => void; timeoutMs: number; } = action;
|
||||
|
||||
action.waitForOwnerTimeoutID = setTimeout(handler, timeoutMs);
|
||||
|
||||
isDialogOpen(store, LoginDialog)
|
||||
|| store.dispatch(openWaitForOwnerDialog());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Will clear the wait for conference owner timeout handler if any is currently
|
||||
* set.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _clearExistingWaitForOwnerTimeout(
|
||||
{ getState }: IStore) {
|
||||
const { waitForOwnerTimeoutID } = getState()['features/authentication'];
|
||||
|
||||
waitForOwnerTimeoutID && clearTimeout(waitForOwnerTimeoutID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cyclic "wait for conference owner" task is currently scheduled.
|
||||
*
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _isWaitingForOwner({ getState }: IStore) {
|
||||
return getState()['features/authentication'].waitForOwnerTimeoutID;
|
||||
}
|
||||
@@ -106,6 +106,15 @@ export const CONFERENCE_UNIQUE_ID_SET = 'CONFERENCE_UNIQUE_ID_SET';
|
||||
*/
|
||||
export const E2E_RTT_CHANGED = 'E2E_RTT_CHANGED'
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a conference will be initialized.
|
||||
*
|
||||
* {
|
||||
* type: CONFERENCE_WILL_INIT
|
||||
* }
|
||||
*/
|
||||
export const CONFERENCE_WILL_INIT = 'CONFERENCE_WILL_INIT';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that a specific conference will be
|
||||
* joined.
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { createStartMutedConfigurationEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { endpointMessageReceived } from '../../subtitles/actions.any';
|
||||
import { iAmVisitor } from '../../visitors/functions';
|
||||
import { getReplaceParticipant } from '../config/functions';
|
||||
import { disconnect } from '../connection/actions';
|
||||
import { 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';
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
CONFERENCE_SUBJECT_CHANGED,
|
||||
CONFERENCE_TIMESTAMP_CHANGED,
|
||||
CONFERENCE_UNIQUE_ID_SET,
|
||||
CONFERENCE_WILL_INIT,
|
||||
CONFERENCE_WILL_JOIN,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
DATA_CHANNEL_CLOSED,
|
||||
@@ -463,6 +463,19 @@ export function _conferenceWillJoin(conference: IJitsiConference) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the intention of the application to have a conference initialized.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CONFERENCE_WILL_INIT
|
||||
* }}
|
||||
*/
|
||||
export function conferenceWillInit() {
|
||||
return {
|
||||
type: CONFERENCE_WILL_INIT
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals the intention of the application to have the local participant
|
||||
* join the specified conference.
|
||||
@@ -647,18 +660,9 @@ export function kickedOut(conference: IJitsiConference, participant: Object) {
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function leaveConference() {
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
|
||||
// FIXME: these should be unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
dispatch(appNavigate(undefined));
|
||||
} else {
|
||||
dispatch(disconnect(true));
|
||||
}
|
||||
};
|
||||
return async (dispatch: IStore['dispatch']) => dispatch(hangup(true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the lock state of a specific JitsiConference changed.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,8 @@ import { openDisplayNamePrompt } from '../../display-name/actions';
|
||||
import { readyToClose } from '../../mobile/external-api/actions';
|
||||
import { showErrorNotification, showWarningNotification } 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';
|
||||
@@ -47,6 +49,7 @@ import {
|
||||
} from './actionTypes';
|
||||
import {
|
||||
conferenceFailed,
|
||||
conferenceWillInit,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
leaveConference,
|
||||
@@ -71,7 +74,7 @@ import logger from './logger';
|
||||
/**
|
||||
* Handler for before unload event.
|
||||
*/
|
||||
let beforeUnloadHandler: (() => void) | undefined;
|
||||
let beforeUnloadHandler: ((e?: any) => void) | undefined;
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature base/conference.
|
||||
@@ -142,12 +145,6 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
function _conferenceFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
const { conference, error } = action;
|
||||
|
||||
if (error.name === JitsiConferenceErrors.REDIRECTED) {
|
||||
if (typeof error.recoverable === 'undefined') {
|
||||
error.recoverable = true;
|
||||
}
|
||||
}
|
||||
|
||||
const result = next(action);
|
||||
const { enableForcedReload } = getState()['features/base/config'];
|
||||
|
||||
@@ -195,18 +192,22 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
break;
|
||||
}
|
||||
case JitsiConferenceErrors.CONFERENCE_MAX_USERS: {
|
||||
if (typeof APP === 'undefined') {
|
||||
// in case of max users(it can be from a visitor node), let's restore
|
||||
// oldConfig if any as we will be back to the main prosody
|
||||
const newConfig = restoreConferenceOptions(getState);
|
||||
dispatch(showErrorNotification({
|
||||
hideErrorSupportLink: true,
|
||||
descriptionKey: 'dialog.maxUsersLimitReached',
|
||||
titleKey: 'dialog.maxUsersLimitReachedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
||||
if (newConfig) {
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(dispatch(conferenceWillLeave(conference)))
|
||||
.then(conference.leave())
|
||||
.then(dispatch(disconnect()))
|
||||
.then(dispatch(connect()));
|
||||
}
|
||||
// In case of max users(it can be from a visitor node), let's restore
|
||||
// oldConfig if any as we will be back to the main prosody.
|
||||
const newConfig = restoreConferenceOptions(getState);
|
||||
|
||||
if (newConfig) {
|
||||
dispatch(overwriteConfig(newConfig)) // @ts-ignore
|
||||
.then(dispatch(conferenceWillLeave(conference)))
|
||||
.then(conference.leave())
|
||||
.then(dispatch(disconnect()))
|
||||
.then(dispatch(connect()));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -214,45 +215,48 @@ function _conferenceFailed({ dispatch, getState }: IStore, next: Function, actio
|
||||
case JitsiConferenceErrors.OFFER_ANSWER_FAILED:
|
||||
sendAnalytics(createOfferAnswerFailedEvent());
|
||||
break;
|
||||
|
||||
case JitsiConferenceErrors.REDIRECTED: {
|
||||
// once conference.js is gone this can be removed and both
|
||||
// redirect logics to be merged
|
||||
if (typeof APP === 'undefined') {
|
||||
const newConfig = getVisitorOptions(getState, error.params);
|
||||
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(conference.leave())
|
||||
.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(connect()));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof APP === 'undefined') {
|
||||
!error.recoverable
|
||||
&& conference
|
||||
&& conference.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
|
||||
// Even though we don't care too much about the failure, it may be
|
||||
// good to know that it happen, so log it (on the info level).
|
||||
logger.info('JitsiConference.leave() rejected with:', reason);
|
||||
});
|
||||
} else {
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
// conference is handled by /conference.js and appropriate failure handlers
|
||||
// are set there.
|
||||
!error.recoverable
|
||||
&& conference
|
||||
&& conference.leave(CONFERENCE_LEAVE_REASONS.UNRECOVERABLE_ERROR).catch((reason: Error) => {
|
||||
// Even though we don't care too much about the failure, it may be
|
||||
// good to know that it happen, so log it (on the info level).
|
||||
logger.info('JitsiConference.leave() rejected with:', reason);
|
||||
});
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
// conference is handled by /conference.js and appropriate failure handlers
|
||||
// are set there.
|
||||
if (typeof APP !== 'undefined') {
|
||||
_removeUnloadHandler(getState);
|
||||
}
|
||||
|
||||
@@ -295,7 +299,14 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
|
||||
// handles the process of leaving the conference. This is temporary solution
|
||||
// that should cover the described use case as part of the effort to
|
||||
// implement the conferenceWillLeave action for web.
|
||||
beforeUnloadHandler = () => {
|
||||
beforeUnloadHandler = (e?: any) => {
|
||||
if (LocalRecordingManager.isRecordingLocally()) {
|
||||
dispatch(stopLocalVideoRecording());
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.returnValue = null;
|
||||
}
|
||||
}
|
||||
dispatch(conferenceWillLeave(conference));
|
||||
};
|
||||
|
||||
@@ -330,12 +341,16 @@ function _conferenceJoined({ dispatch, getState }: IStore, next: Function, actio
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _connectionEstablished({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
async function _connectionEstablished({ dispatch }: IStore, next: Function, action: AnyAction) {
|
||||
const result = next(action);
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
// conference is handled by /conference.js.
|
||||
typeof APP === 'undefined' && dispatch(createConference());
|
||||
if (typeof APP === 'undefined') {
|
||||
dispatch(createConference());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -377,45 +392,45 @@ function _logJwtErrors(message: string, state: IReduxState) {
|
||||
function _connectionFailed({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
|
||||
_logJwtErrors(action.error.message, getState());
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
descriptionKey: 'dialog.tokenAuthFailed',
|
||||
titleKey: 'dialog.tokenAuthFailedTitle'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
||||
const result = next(action);
|
||||
|
||||
_removeUnloadHandler(getState);
|
||||
|
||||
// FIXME: Workaround for the web version. Currently, the creation of the
|
||||
// conference is handled by /conference.js and appropriate failure handlers
|
||||
// are set there.
|
||||
if (typeof APP === 'undefined') {
|
||||
const { connection } = action;
|
||||
const { error } = action;
|
||||
const { connection } = action;
|
||||
const { error } = action;
|
||||
|
||||
forEachConference(getState, conference => {
|
||||
// It feels that it would make things easier if JitsiConference
|
||||
// in lib-jitsi-meet would monitor it's connection and emit
|
||||
// CONFERENCE_FAILED when it's dropped. It has more knowledge on
|
||||
// whether it can recover or not. But because the reload screen
|
||||
// and the retry logic is implemented in the app maybe it can be
|
||||
// left this way for now.
|
||||
if (conference.getConnection() === connection) {
|
||||
// XXX Note that on mobile the error type passed to
|
||||
// connectionFailed is always an object with .name property.
|
||||
// This fact needs to be checked prior to enabling this logic on
|
||||
// web.
|
||||
const conferenceAction
|
||||
= conferenceFailed(conference, error.name);
|
||||
forEachConference(getState, conference => {
|
||||
// TODO: revisit this
|
||||
// It feels that it would make things easier if JitsiConference
|
||||
// in lib-jitsi-meet would monitor it's connection and emit
|
||||
// CONFERENCE_FAILED when it's dropped. It has more knowledge on
|
||||
// whether it can recover or not. But because the reload screen
|
||||
// and the retry logic is implemented in the app maybe it can be
|
||||
// left this way for now.
|
||||
if (conference.getConnection() === connection) {
|
||||
// XXX Note that on mobile the error type passed to
|
||||
// connectionFailed is always an object with .name property.
|
||||
// This fact needs to be checked prior to enabling this logic on
|
||||
// web.
|
||||
const conferenceAction = conferenceFailed(conference, error.name);
|
||||
|
||||
// Copy the recoverable flag if set on the CONNECTION_FAILED
|
||||
// action to not emit recoverable action caused by
|
||||
// a non-recoverable one.
|
||||
if (typeof error.recoverable !== 'undefined') {
|
||||
conferenceAction.error.recoverable = error.recoverable;
|
||||
}
|
||||
|
||||
dispatch(conferenceAction);
|
||||
// Copy the recoverable flag if set on the CONNECTION_FAILED
|
||||
// action to not emit recoverable action caused by
|
||||
// a non-recoverable one.
|
||||
if (typeof error.recoverable !== 'undefined') {
|
||||
conferenceAction.error.recoverable = error.recoverable;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
dispatch(conferenceAction);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_JOIN_IN_PROGRESS,
|
||||
CONFERENCE_LEFT, KICKED_OUT
|
||||
CONFERENCE_LEFT,
|
||||
KICKED_OUT
|
||||
} from './actionTypes';
|
||||
import logger from './logger';
|
||||
import './middleware.any';
|
||||
|
||||
@@ -155,6 +155,9 @@ export interface IConferenceState {
|
||||
|
||||
export interface IJitsiConferenceRoom {
|
||||
locked: boolean;
|
||||
moderator: {
|
||||
logout: Function;
|
||||
};
|
||||
myroomjid: string;
|
||||
roomjid: string;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { setPrejoinDisplayNameRequired } from '../../prejoin/actions.any';
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
appendURLParam,
|
||||
getBackendSafeRoomName
|
||||
@@ -10,8 +14,10 @@ import {
|
||||
CONNECTION_DISCONNECTED,
|
||||
CONNECTION_ESTABLISHED,
|
||||
CONNECTION_FAILED,
|
||||
CONNECTION_WILL_CONNECT,
|
||||
SET_LOCATION_URL
|
||||
} from './actionTypes';
|
||||
import { JITSI_CONNECTION_URL_KEY } from './constants';
|
||||
import logger from './logger';
|
||||
|
||||
/**
|
||||
@@ -194,3 +200,194 @@ export function setLocationURL(locationURL?: URL) {
|
||||
locationURL
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
|
||||
* @param {string} [password] - The XMPP user's password.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function _connectInternal(id?: string, password?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const options = constructOptions(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
|
||||
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
|
||||
|
||||
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
|
||||
|
||||
dispatch(_connectionWillConnect(connection));
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
_onConnectionDisconnected);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
_onConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
|
||||
/**
|
||||
* Marks the display name for the prejoin screen as required.
|
||||
* This can happen if a user tries to join a room with lobby enabled.
|
||||
*/
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.DISPLAY_NAME_REQUIRED,
|
||||
() => dispatch(setPrejoinDisplayNameRequired())
|
||||
);
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
* {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED, _onConnectionDisconnected);
|
||||
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_FAILED, _onConnectionFailed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
|
||||
* disconnected.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionDisconnected() {
|
||||
unsubscribe();
|
||||
dispatch(connectionDisconnected(connection));
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {JitsiConnectionErrors} err - Connection error.
|
||||
* @param {string} [message] - Error message supplied by lib-jitsi-meet.
|
||||
* @param {Object} [credentials] - The invalid credentials that were
|
||||
* used to authenticate and the authentication failed.
|
||||
* @param {string} [credentials.jid] - The XMPP user's ID.
|
||||
* @param {string} [credentials.password] - The XMPP user's password.
|
||||
* @param {Object} details - Additional information about the error.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionFailed( // eslint-disable-line max-params
|
||||
err: string,
|
||||
message: string,
|
||||
credentials: any,
|
||||
details: Object) {
|
||||
unsubscribe();
|
||||
|
||||
dispatch(connectionFailed(connection, {
|
||||
credentials,
|
||||
details,
|
||||
name: err,
|
||||
message
|
||||
}));
|
||||
|
||||
reject(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves external promise when connection is established.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionEstablished() {
|
||||
connection.removeEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED, _onConnectionEstablished);
|
||||
dispatch(connectionEstablished(connection, Date.now()));
|
||||
resolve(connection);
|
||||
}
|
||||
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a connection will connect.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The {@code JitsiConnection} which will
|
||||
* connect.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_WILL_CONNECT,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
function _connectionWillConnect(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_WILL_CONNECT,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
|
||||
const state = getState();
|
||||
|
||||
// The conference we have already joined or are joining.
|
||||
const conference_ = getCurrentConference(state);
|
||||
|
||||
// Promise which completes when the conference has been left and the
|
||||
// connection has been disconnected.
|
||||
let promise;
|
||||
|
||||
// Leave the conference.
|
||||
if (conference_) {
|
||||
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event
|
||||
// (and the respective Redux action) which is fired after the
|
||||
// conference has been left, notify the application about the
|
||||
// intention to leave the conference.
|
||||
dispatch(conferenceWillLeave(conference_));
|
||||
|
||||
promise
|
||||
= conference_.leave()
|
||||
.catch((error: Error) => {
|
||||
logger.warn(
|
||||
'JitsiConference.leave() rejected with:',
|
||||
error);
|
||||
|
||||
// The library lib-jitsi-meet failed to make the
|
||||
// JitsiConference leave. Which may be because
|
||||
// JitsiConference thinks it has already left.
|
||||
// Regardless of the failure reason, continue in
|
||||
// jitsi-meet as if the leave has succeeded.
|
||||
dispatch(conferenceLeft(conference_));
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
// Disconnect the connection.
|
||||
const { connecting, connection } = state['features/base/connection'];
|
||||
|
||||
// The connection we have already connected or are connecting.
|
||||
const connection_ = connection || connecting;
|
||||
|
||||
if (connection_) {
|
||||
promise = promise.then(() => connection_.disconnect());
|
||||
} else {
|
||||
logger.info('No connection found while disconnecting.');
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
import { appNavigate } from '../../app/actions.native';
|
||||
import { IStore } from '../../app/types';
|
||||
import { conferenceLeft, conferenceWillLeave } from '../conference/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import JitsiMeetJS, { JitsiConnectionEvents } from '../lib-jitsi-meet';
|
||||
|
||||
import {
|
||||
CONNECTION_WILL_CONNECT
|
||||
} from './actionTypes';
|
||||
import {
|
||||
connectionDisconnected,
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
constructOptions
|
||||
} from './actions.any';
|
||||
import { JITSI_CONNECTION_URL_KEY } from './constants';
|
||||
import logger from './logger';
|
||||
import { _connectInternal } from './actions.any';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
@@ -25,178 +13,16 @@ export * from './actions.any';
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function connect(id?: string, password?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
const options = constructOptions(state);
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const connection = new JitsiMeetJS.JitsiConnection(options.appId, jwt, options);
|
||||
|
||||
connection[JITSI_CONNECTION_URL_KEY] = locationURL;
|
||||
|
||||
dispatch(_connectionWillConnect(connection));
|
||||
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
_onConnectionDisconnected);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
_onConnectionEstablished);
|
||||
connection.addEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
|
||||
connection.connect({
|
||||
id,
|
||||
password
|
||||
});
|
||||
|
||||
/**
|
||||
* Dispatches {@code CONNECTION_DISCONNECTED} action when connection is
|
||||
* disconnected.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionDisconnected() {
|
||||
unsubscribe();
|
||||
dispatch(connectionDisconnected(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves external promise when connection is established.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionEstablished() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_ESTABLISHED,
|
||||
_onConnectionEstablished);
|
||||
dispatch(connectionEstablished(connection, Date.now()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects external promise when connection fails.
|
||||
*
|
||||
* @param {JitsiConnectionErrors} err - Connection error.
|
||||
* @param {string} [msg] - Error message supplied by lib-jitsi-meet.
|
||||
* @param {Object} [credentials] - The invalid credentials that were
|
||||
* used to authenticate and the authentication failed.
|
||||
* @param {string} [credentials.jid] - The XMPP user's ID.
|
||||
* @param {string} [credentials.password] - The XMPP user's password.
|
||||
* @param {Object} details - Additional information about the error.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onConnectionFailed( // eslint-disable-line max-params
|
||||
err: string,
|
||||
msg: string,
|
||||
credentials: any,
|
||||
details: Object) {
|
||||
unsubscribe();
|
||||
dispatch(
|
||||
connectionFailed(
|
||||
connection, {
|
||||
credentials,
|
||||
details,
|
||||
name: err,
|
||||
message: msg
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe the connection instance from
|
||||
* {@code CONNECTION_DISCONNECTED} and {@code CONNECTION_FAILED} events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function unsubscribe() {
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_DISCONNECTED,
|
||||
_onConnectionDisconnected);
|
||||
connection.removeEventListener(
|
||||
JitsiConnectionEvents.CONNECTION_FAILED,
|
||||
_onConnectionFailed);
|
||||
}
|
||||
};
|
||||
return (dispatch: IStore['dispatch']) => dispatch(_connectInternal(id, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an action for when a connection will connect.
|
||||
* Hangup.
|
||||
*
|
||||
* @param {JitsiConnection} connection - The {@code JitsiConnection} which will
|
||||
* connect.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: CONNECTION_WILL_CONNECT,
|
||||
* connection: JitsiConnection
|
||||
* }}
|
||||
*/
|
||||
function _connectionWillConnect(connection: Object) {
|
||||
return {
|
||||
type: CONNECTION_WILL_CONNECT,
|
||||
connection
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @param {boolean} _ - Used in web.
|
||||
* @param {boolean} [_requestFeedback] - Whether to attempt showing a
|
||||
* request for call feedback.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect(_?: boolean) {
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']): Promise<void> => {
|
||||
const state = getState();
|
||||
|
||||
// The conference we have already joined or are joining.
|
||||
const conference_ = getCurrentConference(state);
|
||||
|
||||
// Promise which completes when the conference has been left and the
|
||||
// connection has been disconnected.
|
||||
let promise;
|
||||
|
||||
// Leave the conference.
|
||||
if (conference_) {
|
||||
// In a fashion similar to JitsiConference's CONFERENCE_LEFT event
|
||||
// (and the respective Redux action) which is fired after the
|
||||
// conference has been left, notify the application about the
|
||||
// intention to leave the conference.
|
||||
dispatch(conferenceWillLeave(conference_));
|
||||
|
||||
promise
|
||||
= conference_.leave()
|
||||
.catch((error: Error) => {
|
||||
logger.warn(
|
||||
'JitsiConference.leave() rejected with:',
|
||||
error);
|
||||
|
||||
// The library lib-jitsi-meet failed to make the
|
||||
// JitsiConference leave. Which may be because
|
||||
// JitsiConference thinks it has already left.
|
||||
// Regardless of the failure reason, continue in
|
||||
// jitsi-meet as if the leave has succeeded.
|
||||
dispatch(conferenceLeft(conference_));
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
}
|
||||
|
||||
// Disconnect the connection.
|
||||
const { connecting, connection } = state['features/base/connection'];
|
||||
|
||||
// The connection we have already connected or are connecting.
|
||||
const connection_ = connection || connecting;
|
||||
|
||||
if (connection_) {
|
||||
promise = promise.then(() => connection_.disconnect());
|
||||
} else {
|
||||
logger.info('No connection found while disconnecting.');
|
||||
}
|
||||
|
||||
return promise;
|
||||
};
|
||||
export function hangup(_requestFeedback = false) {
|
||||
return (dispatch: IStore['dispatch']) => dispatch(appNavigate(undefined));
|
||||
}
|
||||
|
||||
@@ -1,47 +1,81 @@
|
||||
import { IStore } from '../../app/types';
|
||||
import { configureInitialDevices } from '../devices/actions';
|
||||
import { getBackendSafeRoomName } from '../util/uri';
|
||||
// @ts-expect-error
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
|
||||
export {
|
||||
connectionDisconnected,
|
||||
connectionEstablished,
|
||||
connectionFailed,
|
||||
setLocationURL
|
||||
} from './actions.any';
|
||||
import logger from './logger';
|
||||
import { IStore } from '../../app/types';
|
||||
import { getCustomerDetails } from '../../jaas/actions.any';
|
||||
import { getJaasJWT, isVpaasMeeting } from '../../jaas/functions';
|
||||
import { showWarningNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { stopLocalVideoRecording } from '../../recording/actions.any';
|
||||
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web';
|
||||
import { setJWT } from '../jwt/actions';
|
||||
|
||||
import { _connectInternal } from './actions.any';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Opens new connection.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
* @param {string} [id] - The XMPP user's ID (e.g. {@code user@server.com}).
|
||||
* @param {string} [password] - The XMPP user's password.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function connect() {
|
||||
export function connect(id?: string, password?: string) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const room = getBackendSafeRoomName(getState()['features/base/conference'].room);
|
||||
const state = getState();
|
||||
const { jwt } = state['features/base/jwt'];
|
||||
const { iAmRecorder, iAmSipGateway } = state['features/base/config'];
|
||||
|
||||
// XXX For web based version we use conference initialization logic
|
||||
// from the old app (at the moment of writing).
|
||||
return dispatch(configureInitialDevices()).then(
|
||||
() => APP.conference.init({
|
||||
roomName: room
|
||||
}).catch((error: Error) => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
}));
|
||||
if (!iAmRecorder && !iAmSipGateway && isVpaasMeeting(state)) {
|
||||
return dispatch(getCustomerDetails())
|
||||
.then(() => {
|
||||
if (!jwt) {
|
||||
return getJaasJWT(state);
|
||||
}
|
||||
})
|
||||
.then(j => j && dispatch(setJWT(j)))
|
||||
.then(() => _connectInternal(id, password));
|
||||
}
|
||||
|
||||
// used by jibri
|
||||
const usernameOverride = jitsiLocalStorage.getItem('xmpp_username_override');
|
||||
const passwordOverride = jitsiLocalStorage.getItem('xmpp_password_override');
|
||||
|
||||
if (usernameOverride && usernameOverride.length > 0) {
|
||||
id = usernameOverride; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
if (passwordOverride && passwordOverride.length > 0) {
|
||||
password = passwordOverride; // eslint-disable-line no-param-reassign
|
||||
}
|
||||
|
||||
return dispatch(_connectInternal(id, password));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes connection.
|
||||
*
|
||||
* @param {boolean} [requestFeedback] - Whether or not to attempt showing a
|
||||
* @param {boolean} [requestFeedback] - Whether to attempt showing a
|
||||
* request for call feedback.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function disconnect(requestFeedback = false) {
|
||||
// XXX For web based version we use conference hanging up logic from the old
|
||||
// app.
|
||||
return () => APP.conference.hangup(requestFeedback);
|
||||
export function hangup(requestFeedback = false) {
|
||||
// XXX For web based version we use conference hanging up logic from the old app.
|
||||
return async (dispatch: IStore['dispatch']) => {
|
||||
if (LocalRecordingManager.isRecordingLocally()) {
|
||||
dispatch(stopLocalVideoRecording());
|
||||
dispatch(showWarningNotification({
|
||||
titleKey: 'localRecording.stopping',
|
||||
descriptionKey: 'localRecording.wait'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
|
||||
// wait 1000ms for the recording to end and start downloading
|
||||
await new Promise(res => {
|
||||
setTimeout(res, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
return APP.conference.hangup(requestFeedback);
|
||||
};
|
||||
}
|
||||
|
||||
30
react/features/base/connection/middleware.web.ts
Normal file
30
react/features/base/connection/middleware.web.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
|
||||
import { CONNECTION_WILL_CONNECT } from './actionTypes';
|
||||
|
||||
/**
|
||||
* The feature announced so we can distinguish jibri participants.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const DISCO_JIBRI_FEATURE = 'http://jitsi.org/protocol/jibri';
|
||||
|
||||
MiddlewareRegistry.register(({ getState }) => next => action => {
|
||||
switch (action.type) {
|
||||
case CONNECTION_WILL_CONNECT: {
|
||||
const { connection } = action;
|
||||
const { iAmRecorder } = getState()['features/base/config'];
|
||||
|
||||
if (iAmRecorder) {
|
||||
connection.addFeature(DISCO_JIBRI_FEATURE);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
APP.connection = connection;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
@@ -243,6 +243,12 @@ export const TOOLBOX_ALWAYS_VISIBLE = 'toolbox.alwaysVisible';
|
||||
*/
|
||||
export const TOOLBOX_ENABLED = 'toolbox.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the unsafe room warning should be enabled.
|
||||
* Default: disabled (false).
|
||||
*/
|
||||
export const UNSAFE_ROOM_WARNING = 'unsaferoomwarning.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the video mute button should be displayed.
|
||||
* Default: enabled (true).
|
||||
|
||||
@@ -142,7 +142,7 @@ export function createLocalTracksA(options: ITrackOptions = {}) {
|
||||
const promises = [];
|
||||
|
||||
// The following executes on React Native only at the time of this
|
||||
// writing. The effort to port Web's createInitialLocalTracksAndConnect
|
||||
// writing. The effort to port Web's createInitialLocalTracks
|
||||
// is significant and that's where the function createLocalTracksF got
|
||||
// born. I started with the idea a porting so that we could inherit the
|
||||
// ability to getUserMedia for audio only or video only if getUserMedia
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { sendMessage, setIsPollsTabFocused } from '../actions';
|
||||
import { SMALL_WIDTH_THRESHOLD } from '../constants';
|
||||
import { IMessage } from '../reducer';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChat}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether the chat is opened in a modal or not (computed based on window width).
|
||||
*/
|
||||
_isModal: boolean;
|
||||
|
||||
/**
|
||||
* True if the chat window should be rendered.
|
||||
*/
|
||||
_isOpen: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is enabled.
|
||||
*/
|
||||
_isPollsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the poll tab is focused or not.
|
||||
*/
|
||||
_isPollsTabFocused: boolean;
|
||||
|
||||
/**
|
||||
* All the chat messages in the conference.
|
||||
*/
|
||||
_messages: IMessage[];
|
||||
|
||||
/**
|
||||
* Number of unread chat messages.
|
||||
*/
|
||||
_nbUnreadMessages: number;
|
||||
|
||||
/**
|
||||
* Number of unread poll messages.
|
||||
*/
|
||||
_nbUnreadPolls: number;
|
||||
|
||||
/**
|
||||
* Function to send a text message.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onSendMessage: Function;
|
||||
|
||||
/**
|
||||
* Function to toggle the chat window.
|
||||
*/
|
||||
_onToggleChat: Function;
|
||||
|
||||
/**
|
||||
* Function to display the chat tab.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onToggleChatTab: Function;
|
||||
|
||||
/**
|
||||
* Function to display the polls tab.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onTogglePollsTab: Function;
|
||||
|
||||
/**
|
||||
* Whether or not to block chat access with a nickname input form.
|
||||
*/
|
||||
_showNamePrompt: boolean;
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract chat panel.
|
||||
*/
|
||||
export default class AbstractChat<P extends IProps> extends Component<P> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractChat} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractChat} instance with.
|
||||
*/
|
||||
constructor(props: P) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSendMessage = this._onSendMessage.bind(this);
|
||||
this._onToggleChatTab = this._onToggleChatTab.bind(this);
|
||||
this._onTogglePollsTab = this._onTogglePollsTab.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onSendMessage(text: string) {
|
||||
this.props.dispatch(sendMessage(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Chat tab.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggleChatTab() {
|
||||
this.props.dispatch(setIsPollsTabFocused(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the Polls tab.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onTogglePollsTab() {
|
||||
this.props.dispatch(setIsPollsTabFocused(true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _isOpen: boolean,
|
||||
* _messages: Array<Object>,
|
||||
* _showNamePrompt: boolean
|
||||
* }}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, isPollsTabFocused, messages, nbUnreadMessages } = state['features/chat'];
|
||||
const { nbUnreadPolls } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
|
||||
_isOpen: isOpen,
|
||||
_isPollsEnabled: !disablePolls,
|
||||
_isPollsTabFocused: isPollsTabFocused,
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages,
|
||||
_nbUnreadPolls: nbUnreadPolls,
|
||||
_showNamePrompt: !_localParticipant?.name
|
||||
};
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { PureComponent } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { getLocalizedDateFormatter } from '../../base/i18n/dateUtil';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL } from '../constants';
|
||||
import { IMessage } from '../reducer';
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
*/
|
||||
const TIMESTAMP_FORMAT = 'H:mm';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChatMessage}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether current participant is currently knocking in the lobby room.
|
||||
*/
|
||||
knocking: boolean;
|
||||
|
||||
/**
|
||||
* The representation of a chat message.
|
||||
*/
|
||||
message: IMessage;
|
||||
|
||||
/**
|
||||
* Whether or not the avatar image of the participant which sent the message
|
||||
* should be displayed.
|
||||
*/
|
||||
showAvatar?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the name of the participant which sent the message should
|
||||
* be displayed.
|
||||
*/
|
||||
showDisplayName: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the time at which the message was sent should be
|
||||
* displayed.
|
||||
*/
|
||||
showTimestamp: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract component to display a chat message.
|
||||
*/
|
||||
export default class AbstractChatMessage<P extends IProps> extends PureComponent<P> {
|
||||
/**
|
||||
* Returns the timestamp to display for the message.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getFormattedTimestamp() {
|
||||
return getLocalizedDateFormatter(new Date(this.props.message.timestamp))
|
||||
.format(TIMESTAMP_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the message text to be rendered in the component.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getMessageText() {
|
||||
const { message } = this.props;
|
||||
|
||||
return message.messageType === MESSAGE_TYPE_ERROR
|
||||
? this.props.t('chat.error', {
|
||||
error: message.message
|
||||
})
|
||||
: message.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message that is displayed as a notice for private messages.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
_getPrivateNoticeMessage() {
|
||||
const { message, t } = this.props;
|
||||
|
||||
return t('chat.privateNotice', {
|
||||
recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : t('chat.you')
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component } from 'react';
|
||||
|
||||
import { IMessage } from '../reducer';
|
||||
import { IMessage } from '../types';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
/* eslint-disable react/no-multi-comp */
|
||||
import { Route, useIsFocused } from '@react-navigation/native';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { Component, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import JitsiScreen from '../../../base/modal/components/JitsiScreen';
|
||||
import { TabBarLabelCounter } from '../../../mobile/navigation/components/TabBarLabelCounter';
|
||||
import { closeChat } from '../../actions.native';
|
||||
import AbstractChat, {
|
||||
IProps as AbstractProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractChat';
|
||||
import { closeChat, sendMessage } from '../../actions.native';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatInputBar from './ChatInputBar';
|
||||
import MessageContainer from './MessageContainer';
|
||||
@@ -34,7 +32,21 @@ interface IProps extends AbstractProps {
|
||||
* Implements a React native component that renders the chat window (modal) of
|
||||
* the mobile client.
|
||||
*/
|
||||
class Chat extends AbstractChat<IProps> {
|
||||
class Chat extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Initializes a new {@code AbstractChat} instance.
|
||||
*
|
||||
* @param {Props} props - The React {@code Component} props to initialize
|
||||
* the new {@code AbstractChat} instance with.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSendMessage = this._onSendMessage.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -57,6 +69,39 @@ class Chat extends AbstractChat<IProps> {
|
||||
</JitsiScreen>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
_onSendMessage(text: string) {
|
||||
this.props.dispatch(sendMessage(text));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { messages, nbUnreadMessages } = state['features/chat'];
|
||||
|
||||
return {
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)((props: IProps) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { Text, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
@@ -8,8 +8,13 @@ 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 { replaceNonUnicodeEmojis } from '../../functions';
|
||||
import AbstractChatMessage, { IProps } from '../AbstractChatMessage';
|
||||
import {
|
||||
getFormattedTimestamp,
|
||||
getMessageText,
|
||||
getPrivateNoticeMessage,
|
||||
replaceNonUnicodeEmojis
|
||||
} from '../../functions';
|
||||
import { IChatMessageProps } from '../../types';
|
||||
|
||||
import GifMessage from './GifMessage';
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
@@ -19,7 +24,7 @@ import styles from './styles';
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*/
|
||||
class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
class ChatMessage extends Component<IChatMessageProps> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
@@ -67,7 +72,7 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
messageBubbleStyle.push(styles.lobbyMessageBubble);
|
||||
}
|
||||
|
||||
const messageText = replaceNonUnicodeEmojis(this._getMessageText());
|
||||
const messageText = replaceNonUnicodeEmojis(getMessageText(this.props.message));
|
||||
|
||||
return (
|
||||
<View style = { styles.messageWrapper as ViewStyle } >
|
||||
@@ -147,7 +152,7 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
|
||||
return (
|
||||
<Text style = { message.lobbyChat ? styles.lobbyMsgNotice : styles.privateNotice }>
|
||||
{ this._getPrivateNoticeMessage() }
|
||||
{ getPrivateNoticeMessage(this.props.message) }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
@@ -189,7 +194,7 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
|
||||
return (
|
||||
<Text style = { styles.timeText }>
|
||||
{ this._getFormattedTimestamp() }
|
||||
{ getFormattedTimestamp(this.props.message) }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import { FlatList } from 'react-native';
|
||||
|
||||
import { MESSAGE_TYPE_LOCAL, MESSAGE_TYPE_REMOTE } from '../../constants';
|
||||
import { IMessage } from '../../reducer';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { FlatList, Text, TextStyle, View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { IMessage } from '../../reducer';
|
||||
import { IMessage } from '../../types';
|
||||
import AbstractMessageContainer, { IProps as AbstractProps } from '../AbstractMessageContainer';
|
||||
|
||||
import ChatMessageGroup from './ChatMessageGroup';
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { getLocalParticipant } from '../../../base/participants/functions';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import Tabs from '../../../base/ui/components/web/Tabs';
|
||||
import PollsPane from '../../../polls/components/web/PollsPane';
|
||||
import { toggleChat } from '../../actions.web';
|
||||
import { CHAT_TABS } from '../../constants';
|
||||
import AbstractChat, {
|
||||
IProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractChat';
|
||||
import { sendMessage, setIsPollsTabFocused, toggleChat } from '../../actions.web';
|
||||
import { CHAT_SIZE, CHAT_TABS, SMALL_WIDTH_THRESHOLD } from '../../constants';
|
||||
import { IChatProps as AbstractProps } from '../../types';
|
||||
|
||||
import ChatHeader from './ChatHeader';
|
||||
import ChatInput from './ChatInput';
|
||||
@@ -19,75 +19,167 @@ import KeyboardAvoider from './KeyboardAvoider';
|
||||
import MessageContainer from './MessageContainer';
|
||||
import MessageRecipient from './MessageRecipient';
|
||||
|
||||
/**
|
||||
* React Component for holding the chat feature in a side panel that slides in
|
||||
* and out of view.
|
||||
*/
|
||||
class Chat extends AbstractChat<IProps> {
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
/**
|
||||
* Reference to the React Component for displaying chat messages. Used for
|
||||
* scrolling to the end of the chat messages.
|
||||
* Whether the chat is opened in a modal or not (computed based on window width).
|
||||
*/
|
||||
_messageContainerRef: Object;
|
||||
_isModal: boolean;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code Chat} instance.
|
||||
* True if the chat window should be rendered.
|
||||
*/
|
||||
_isOpen: boolean;
|
||||
|
||||
/**
|
||||
* True if the polls feature is enabled.
|
||||
*/
|
||||
_isPollsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Whether the poll tab is focused or not.
|
||||
*/
|
||||
_isPollsTabFocused: boolean;
|
||||
|
||||
/**
|
||||
* Number of unread poll messages.
|
||||
*/
|
||||
_nbUnreadPolls: number;
|
||||
|
||||
/**
|
||||
* Function to send a text message.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
* @protected
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._messageContainerRef = React.createRef();
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onChatTabKeyDown = this._onChatTabKeyDown.bind(this);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
this._onPollsTabKeyDown = this._onPollsTabKeyDown.bind(this);
|
||||
this._onToggleChat = this._onToggleChat.bind(this);
|
||||
this._onChangeTab = this._onChangeTab.bind(this);
|
||||
}
|
||||
_onSendMessage: Function;
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
* Function to toggle the chat window.
|
||||
*/
|
||||
render() {
|
||||
const { _isOpen, _isPollsEnabled, _showNamePrompt } = this.props;
|
||||
|
||||
return (
|
||||
_isOpen ? <div
|
||||
className = 'sideToolbarContainer'
|
||||
id = 'sideToolbarContainer'
|
||||
onKeyDown = { this._onEscClick } >
|
||||
<ChatHeader
|
||||
className = 'chat-header'
|
||||
isPollsEnabled = { _isPollsEnabled }
|
||||
onCancel = { this._onToggleChat } />
|
||||
{ _showNamePrompt
|
||||
? <DisplayNameForm isPollsEnabled = { _isPollsEnabled } />
|
||||
: this._renderChat() }
|
||||
</div> : null
|
||||
);
|
||||
}
|
||||
_onToggleChat: Function;
|
||||
|
||||
/**
|
||||
* Key press handler for the chat tab.
|
||||
* Function to display the chat tab.
|
||||
*
|
||||
* @param {KeyboardEvent} event - The event.
|
||||
* @returns {void}
|
||||
* @protected
|
||||
*/
|
||||
_onChatTabKeyDown(event: React.KeyboardEvent) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onToggleChatTab();
|
||||
_onToggleChatTab: Function;
|
||||
|
||||
/**
|
||||
* Function to display the polls tab.
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_onTogglePollsTab: Function;
|
||||
|
||||
/**
|
||||
* Whether or not to block chat access with a nickname input form.
|
||||
*/
|
||||
_showNamePrompt: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
container: {
|
||||
backgroundColor: theme.palette.ui01,
|
||||
flexShrink: 0,
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
transition: 'width .16s ease-in-out',
|
||||
width: `${CHAT_SIZE}px`,
|
||||
zIndex: 300,
|
||||
|
||||
'@media (max-width: 580px)': {
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: 'auto'
|
||||
},
|
||||
|
||||
'*': {
|
||||
userSelect: 'text',
|
||||
'-webkit-user-select': 'text'
|
||||
}
|
||||
},
|
||||
|
||||
chatHeader: {
|
||||
height: '60px',
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
zIndex: 1,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
padding: `${theme.spacing(3)} ${theme.spacing(4)}`,
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
color: theme.palette.text01,
|
||||
...withPixelLineHeight(theme.typography.heading6),
|
||||
|
||||
'.jitsi-icon': {
|
||||
cursor: 'pointer'
|
||||
}
|
||||
},
|
||||
|
||||
chatPanel: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
// extract header + tabs height
|
||||
height: 'calc(100% - 110px)'
|
||||
},
|
||||
|
||||
chatPanelNoTabs: {
|
||||
// extract header height
|
||||
height: 'calc(100% - 60px)'
|
||||
},
|
||||
|
||||
pollsPanel: {
|
||||
// extract header + tabs height
|
||||
height: 'calc(100% - 110px)'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const Chat = ({
|
||||
_isModal,
|
||||
_isOpen,
|
||||
_isPollsEnabled,
|
||||
_isPollsTabFocused,
|
||||
_messages,
|
||||
_nbUnreadMessages,
|
||||
_nbUnreadPolls,
|
||||
_onSendMessage,
|
||||
_onToggleChat,
|
||||
_onToggleChatTab,
|
||||
_onTogglePollsTab,
|
||||
_showNamePrompt,
|
||||
dispatch,
|
||||
t
|
||||
}: IProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
/**
|
||||
* Sends a text message.
|
||||
*
|
||||
* @private
|
||||
* @param {string} text - The text message to be sent.
|
||||
* @returns {void}
|
||||
* @type {Function}
|
||||
*/
|
||||
const onSendMessage = useCallback((text: string) => {
|
||||
dispatch(sendMessage(text));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
const onToggleChat = useCallback(() => {
|
||||
dispatch(toggleChat());
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Click handler for the chat sidenav.
|
||||
@@ -95,27 +187,23 @@ class Chat extends AbstractChat<IProps> {
|
||||
* @param {KeyboardEvent} event - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscClick(event: React.KeyboardEvent) {
|
||||
if (event.key === 'Escape' && this.props._isOpen) {
|
||||
const onEscClick = useCallback((event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && _isOpen) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onToggleChat();
|
||||
onToggleChat();
|
||||
}
|
||||
}
|
||||
}, [ _isOpen ]);
|
||||
|
||||
/**
|
||||
* Key press handler for the polls tab.
|
||||
* Change selected tab.
|
||||
*
|
||||
* @param {KeyboardEvent} event - The event.
|
||||
* @param {string} id - Id of the clicked tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onPollsTabKeyDown(event: React.KeyboardEvent) {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onTogglePollsTab();
|
||||
}
|
||||
}
|
||||
const onChangeTab = useCallback((id: string) => {
|
||||
dispatch(setIsPollsTabFocused(id !== CHAT_TABS.CHAT));
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Returns a React Element for showing chat messages and a form to send new
|
||||
@@ -124,33 +212,31 @@ class Chat extends AbstractChat<IProps> {
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderChat() {
|
||||
const { _isPollsEnabled, _isPollsTabFocused } = this.props;
|
||||
|
||||
function renderChat() {
|
||||
return (
|
||||
<>
|
||||
{ _isPollsEnabled && this._renderTabs() }
|
||||
{_isPollsEnabled && renderTabs()}
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.CHAT }
|
||||
className = { clsx(
|
||||
'chat-panel',
|
||||
!_isPollsEnabled && 'chat-panel-no-tabs',
|
||||
className = { cx(
|
||||
classes.chatPanel,
|
||||
!_isPollsEnabled && classes.chatPanelNoTabs,
|
||||
_isPollsTabFocused && 'hide'
|
||||
) }
|
||||
id = { `${CHAT_TABS.CHAT}-panel` }
|
||||
role = 'tabpanel'
|
||||
tabIndex = { 0 }>
|
||||
<MessageContainer
|
||||
messages = { this.props._messages } />
|
||||
messages = { _messages } />
|
||||
<MessageRecipient />
|
||||
<ChatInput
|
||||
onSend = { this._onSendMessage } />
|
||||
onSend = { onSendMessage } />
|
||||
</div>
|
||||
{ _isPollsEnabled && (
|
||||
{_isPollsEnabled && (
|
||||
<>
|
||||
<div
|
||||
aria-labelledby = { CHAT_TABS.POLLS }
|
||||
className = { clsx('polls-panel', !_isPollsTabFocused && 'hide') }
|
||||
className = { cx(classes.pollsPanel, !_isPollsTabFocused && 'hide') }
|
||||
id = { `${CHAT_TABS.POLLS}-panel` }
|
||||
role = 'tabpanel'
|
||||
tabIndex = { 0 }>
|
||||
@@ -169,13 +255,11 @@ class Chat extends AbstractChat<IProps> {
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderTabs() {
|
||||
const { _isPollsEnabled, _isPollsTabFocused, _nbUnreadMessages, _nbUnreadPolls, t } = this.props;
|
||||
|
||||
function renderTabs() {
|
||||
return (
|
||||
<Tabs
|
||||
accessibilityLabel = { t(_isPollsEnabled ? 'chat.titleWithPolls' : 'chat.title') }
|
||||
onChange = { this._onChangeTab }
|
||||
onChange = { onChangeTab }
|
||||
selected = { _isPollsTabFocused ? CHAT_TABS.POLLS : CHAT_TABS.CHAT }
|
||||
tabs = { [ {
|
||||
accessibilityLabel: t('chat.tabs.chat'),
|
||||
@@ -194,24 +278,56 @@ class Chat extends AbstractChat<IProps> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the chat window.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
_onToggleChat() {
|
||||
this.props.dispatch(toggleChat());
|
||||
}
|
||||
return (
|
||||
_isOpen ? <div
|
||||
className = { classes.container }
|
||||
id = 'sideToolbarContainer'
|
||||
onKeyDown = { onEscClick } >
|
||||
<ChatHeader
|
||||
className = { cx('chat-header', classes.chatHeader) }
|
||||
isPollsEnabled = { _isPollsEnabled }
|
||||
onCancel = { onToggleChat } />
|
||||
{_showNamePrompt
|
||||
? <DisplayNameForm isPollsEnabled = { _isPollsEnabled } />
|
||||
: renderChat()}
|
||||
</div> : null
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Change selected tab.
|
||||
*
|
||||
* @param {string} id - Id of the clicked tab.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangeTab(id: string) {
|
||||
id === CHAT_TABS.CHAT ? this._onToggleChatTab() : this._onTogglePollsTab();
|
||||
}
|
||||
/**
|
||||
* Maps (parts of) the redux state to {@link Chat} React {@code Component}
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The redux store/state.
|
||||
* @param {any} _ownProps - Components' own props.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _isModal: boolean,
|
||||
* _isOpen: boolean,
|
||||
* _isPollsEnabled: boolean,
|
||||
* _isPollsTabFocused: boolean,
|
||||
* _messages: Array<Object>,
|
||||
* _nbUnreadMessages: number,
|
||||
* _nbUnreadPolls: number,
|
||||
* _showNamePrompt: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, _ownProps: any) {
|
||||
const { isOpen, isPollsTabFocused, messages, nbUnreadMessages } = state['features/chat'];
|
||||
const { nbUnreadPolls } = state['features/polls'];
|
||||
const _localParticipant = getLocalParticipant(state);
|
||||
const { disablePolls } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_isModal: window.innerWidth <= SMALL_WIDTH_THRESHOLD,
|
||||
_isOpen: isOpen,
|
||||
_isPollsEnabled: !disablePolls,
|
||||
_isPollsTabFocused: isPollsTabFocused,
|
||||
_messages: messages,
|
||||
_nbUnreadMessages: nbUnreadMessages,
|
||||
_nbUnreadPolls: nbUnreadPolls,
|
||||
_showNamePrompt: !_localParticipant?.name
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(Chat));
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { Theme } from '@mui/material';
|
||||
import { withStyles } from '@mui/styles';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Message from '../../../base/react/components/web/Message';
|
||||
import { withPixelLineHeight } from '../../../base/styles/functions.web';
|
||||
import { MESSAGE_TYPE_LOCAL } from '../../constants';
|
||||
import AbstractChatMessage, { IProps as AbstractProps } from '../AbstractChatMessage';
|
||||
import { getFormattedTimestamp, getMessageText, getPrivateNoticeMessage } from '../../functions';
|
||||
import { IChatMessageProps } from '../../types';
|
||||
|
||||
import PrivateMessageButton from './PrivateMessageButton';
|
||||
|
||||
interface IProps extends AbstractProps {
|
||||
|
||||
classes: any;
|
||||
interface IProps extends IChatMessageProps {
|
||||
|
||||
type: string;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) => {
|
||||
const useStyles = makeStyles()((theme: Theme) => {
|
||||
return {
|
||||
chatMessageWrapper: {
|
||||
maxWidth: '100%'
|
||||
@@ -110,73 +108,35 @@ const styles = (theme: Theme) => {
|
||||
marginTop: theme.spacing(1)
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Renders a single chat message.
|
||||
*
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { message, t, knocking, classes, type } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { clsx(classes.chatMessageWrapper, type) }
|
||||
id = { this.props.message.messageId }
|
||||
tabIndex = { -1 }>
|
||||
<div
|
||||
className = { clsx('chatmessage', classes.chatMessage, type,
|
||||
message.privateMessage && 'privatemessage',
|
||||
message.lobbyChat && !knocking && 'lobbymessage') }>
|
||||
<div className = { classes.replyWrapper }>
|
||||
<div className = { clsx('messagecontent', classes.messageContent) }>
|
||||
{ this.props.showDisplayName && this._renderDisplayName() }
|
||||
<div className = { clsx('usermessage', classes.userMessage) }>
|
||||
<span className = 'sr-only'>
|
||||
{ this.props.message.displayName === this.props.message.recipient
|
||||
? t('chat.messageAccessibleTitleMe')
|
||||
: t('chat.messageAccessibleTitle',
|
||||
{ user: this.props.message.displayName }) }
|
||||
</span>
|
||||
<Message text = { this._getMessageText() } />
|
||||
</div>
|
||||
{ (message.privateMessage || (message.lobbyChat && !knocking))
|
||||
&& this._renderPrivateNotice() }
|
||||
</div>
|
||||
{ (message.privateMessage || (message.lobbyChat && !knocking))
|
||||
&& message.messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& (
|
||||
<div
|
||||
className = { classes.replyButtonContainer }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
participantID = { message.id } />
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
{ this.props.showTimestamp && this._renderTimestamp() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const ChatMessage = ({
|
||||
knocking,
|
||||
message,
|
||||
showDisplayName,
|
||||
showTimestamp,
|
||||
type,
|
||||
t
|
||||
}: IProps) => {
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
/**
|
||||
* Renders the display name of the sender.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderDisplayName() {
|
||||
function _renderDisplayName() {
|
||||
return (
|
||||
<div
|
||||
aria-hidden = { true }
|
||||
className = { clsx('display-name', this.props.classes.displayName) }>
|
||||
{ this.props.message.displayName }
|
||||
className = { cx('display-name', classes.displayName) }>
|
||||
{message.displayName}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -186,10 +146,10 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderPrivateNotice() {
|
||||
function _renderPrivateNotice() {
|
||||
return (
|
||||
<div className = { this.props.classes.privateMessageNotice }>
|
||||
{ this._getPrivateNoticeMessage() }
|
||||
<div className = { classes.privateMessageNotice }>
|
||||
{getPrivateNoticeMessage(message)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -199,14 +159,54 @@ class ChatMessage extends AbstractChatMessage<IProps> {
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
_renderTimestamp() {
|
||||
function _renderTimestamp() {
|
||||
return (
|
||||
<div className = { clsx('timestamp', this.props.classes.timestamp) }>
|
||||
{ this._getFormattedTimestamp() }
|
||||
<div className = { cx('timestamp', classes.timestamp) }>
|
||||
{getFormattedTimestamp(message)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { cx(classes.chatMessageWrapper, type) }
|
||||
id = { message.messageId }
|
||||
tabIndex = { -1 }>
|
||||
<div
|
||||
className = { cx('chatmessage', classes.chatMessage, type,
|
||||
message.privateMessage && 'privatemessage',
|
||||
message.lobbyChat && !knocking && 'lobbymessage') }>
|
||||
<div className = { classes.replyWrapper }>
|
||||
<div className = { cx('messagecontent', classes.messageContent) }>
|
||||
{showDisplayName && _renderDisplayName()}
|
||||
<div className = { cx('usermessage', classes.userMessage) }>
|
||||
<span className = 'sr-only'>
|
||||
{message.displayName === message.recipient
|
||||
? t('chat.messageAccessibleTitleMe')
|
||||
: t('chat.messageAccessibleTitle',
|
||||
{ user: message.displayName })}
|
||||
</span>
|
||||
<Message text = { getMessageText(message) } />
|
||||
</div>
|
||||
{(message.privateMessage || (message.lobbyChat && !knocking))
|
||||
&& _renderPrivateNotice()}
|
||||
</div>
|
||||
{(message.privateMessage || (message.lobbyChat && !knocking))
|
||||
&& message.messageType !== MESSAGE_TYPE_LOCAL
|
||||
&& (
|
||||
<div
|
||||
className = { classes.replyButtonContainer }>
|
||||
<PrivateMessageButton
|
||||
isLobbyMessage = { message.lobbyChat }
|
||||
participantID = { message.id } />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{showTimestamp && _renderTimestamp()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps part of the Redux store to the props of this component.
|
||||
@@ -222,4 +222,4 @@ function _mapStateToProps(state: IReduxState) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(withStyles(styles)(ChatMessage)));
|
||||
export default translate(connect(_mapStateToProps)(ChatMessage));
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import Avatar from '../../../base/avatar/components/Avatar';
|
||||
import { IMessage } from '../../reducer';
|
||||
import { IMessage } from '../../types';
|
||||
|
||||
import ChatMessage from './ChatMessage';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* The size of the chat.
|
||||
* The size of the chat. Equal to $sidebarWidth SCSS variable.
|
||||
*/
|
||||
export const CHAT_SIZE = 315;
|
||||
|
||||
@@ -38,3 +38,8 @@ export const CHAT_TABS = {
|
||||
POLLS: 'polls-tab',
|
||||
CHAT: 'chat-tab'
|
||||
};
|
||||
|
||||
/**
|
||||
* Formatter string to display the message timestamp.
|
||||
*/
|
||||
export const TIMESTAMP_FORMAT = 'H:mm';
|
||||
|
||||
@@ -5,9 +5,12 @@ import aliases from 'react-emoji-render/data/aliases';
|
||||
import emojiAsciiAliases from 'react-emoji-render/data/asciiAliases';
|
||||
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getLocalizedDateFormatter } from '../base/i18n/dateUtil';
|
||||
import i18next from '../base/i18n/i18next';
|
||||
import { escapeRegexp } from '../base/util/helpers';
|
||||
|
||||
import { IMessage } from './reducer';
|
||||
import { MESSAGE_TYPE_ERROR, MESSAGE_TYPE_LOCAL, TIMESTAMP_FORMAT } from './constants';
|
||||
import { IMessage } from './types';
|
||||
|
||||
/**
|
||||
* An ASCII emoticon regexp array to find and replace old-style ASCII
|
||||
@@ -132,3 +135,40 @@ export function areSmileysDisabled(state: IReduxState) {
|
||||
|
||||
return disableChatSmileys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp to display for the message.
|
||||
*
|
||||
* @param {IMessage} message - The message from which to get the timestamp.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getFormattedTimestamp(message: IMessage) {
|
||||
return getLocalizedDateFormatter(new Date(message.timestamp))
|
||||
.format(TIMESTAMP_FORMAT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the message text to be rendered in the component.
|
||||
*
|
||||
* @param {IMessage} message - The message from which to get the text.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getMessageText(message: IMessage) {
|
||||
return message.messageType === MESSAGE_TYPE_ERROR
|
||||
? i18next.t('chat.error', {
|
||||
error: message.message
|
||||
})
|
||||
: message.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message that is displayed as a notice for private messages.
|
||||
*
|
||||
* @param {IMessage} message - The message to be checked.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getPrivateNoticeMessage(message: IMessage) {
|
||||
return i18next.t('chat.privateNotice', {
|
||||
recipient: message.messageType === MESSAGE_TYPE_LOCAL ? message.recipient : i18next.t('chat.you')
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
SET_LOBBY_CHAT_RECIPIENT,
|
||||
SET_PRIVATE_MESSAGE_RECIPIENT
|
||||
} from './actionTypes';
|
||||
import { IMessage } from './types';
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
isOpen: false,
|
||||
@@ -27,20 +28,6 @@ const DEFAULT_STATE = {
|
||||
isLobbyChatActive: false
|
||||
};
|
||||
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
id: string;
|
||||
isReaction: boolean;
|
||||
lobbyChat: boolean;
|
||||
message: string;
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
privateMessage: boolean;
|
||||
recipient: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface IChatState {
|
||||
isLobbyChatActive: boolean;
|
||||
isOpen: boolean;
|
||||
|
||||
69
react/features/chat/types.ts
Normal file
69
react/features/chat/types.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
|
||||
export interface IMessage {
|
||||
displayName: string;
|
||||
error?: Object;
|
||||
id: string;
|
||||
isReaction: boolean;
|
||||
lobbyChat: boolean;
|
||||
message: string;
|
||||
messageId: string;
|
||||
messageType: string;
|
||||
privateMessage: boolean;
|
||||
recipient: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@code AbstractChat}.
|
||||
*/
|
||||
export interface IChatProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* All the chat messages in the conference.
|
||||
*/
|
||||
_messages: IMessage[];
|
||||
|
||||
/**
|
||||
* Number of unread chat messages.
|
||||
*/
|
||||
_nbUnreadMessages: number;
|
||||
|
||||
/**
|
||||
* The Redux dispatch function.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
export interface IChatMessageProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Whether current participant is currently knocking in the lobby room.
|
||||
*/
|
||||
knocking: boolean;
|
||||
|
||||
/**
|
||||
* The representation of a chat message.
|
||||
*/
|
||||
message: IMessage;
|
||||
|
||||
/**
|
||||
* Whether or not the avatar image of the participant which sent the message
|
||||
* should be displayed.
|
||||
*/
|
||||
showAvatar?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the name of the participant which sent the message should
|
||||
* be displayed.
|
||||
*/
|
||||
showDisplayName: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the time at which the message was sent should be
|
||||
* displayed.
|
||||
*/
|
||||
showTimestamp: boolean;
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { configureInitialDevices } from '../base/devices/actions.web';
|
||||
import { getParticipantDisplayName } from '../base/participants/functions';
|
||||
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 logger from './logger';
|
||||
|
||||
/**
|
||||
* Notify that we've been kicked out of the conference.
|
||||
@@ -45,3 +48,24 @@ export function dismissCalendarNotification() {
|
||||
type: DISMISS_CALENDAR_NOTIFICATION
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
export function init() {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const room = getBackendSafeRoomName(getState()['features/base/conference'].room);
|
||||
|
||||
// XXX For web based version we use conference initialization logic
|
||||
// from the old app (at the moment of writing).
|
||||
return dispatch(configureInitialDevices()).then(
|
||||
() => APP.conference.init({
|
||||
roomName: room
|
||||
}).catch((error: Error) => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
logger.error(error);
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { connect as reactReduxConnect } from 'react-redux';
|
||||
import VideoLayout from '../../../../../modules/UI/videolayout/VideoLayout';
|
||||
import { IReduxState, IStore } from '../../../app/types';
|
||||
import { getConferenceNameForTitle } from '../../../base/conference/functions';
|
||||
import { connect, disconnect } from '../../../base/connection/actions.web';
|
||||
import { hangup } from '../../../base/connection/actions.web';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import { setColorAlpha } from '../../../base/util/helpers';
|
||||
@@ -29,6 +29,7 @@ import JitsiPortal from '../../../toolbox/components/web/JitsiPortal';
|
||||
import Toolbox from '../../../toolbox/components/web/Toolbox';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../video-layout/constants';
|
||||
import { getCurrentLayout } from '../../../video-layout/functions.any';
|
||||
import { init } from '../../actions.web';
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../../functions.web';
|
||||
import {
|
||||
AbstractConference,
|
||||
@@ -187,7 +188,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
FULL_SCREEN_EVENTS.forEach(name =>
|
||||
document.removeEventListener(name, this._onFullScreenChange));
|
||||
|
||||
APP.conference.isJoined() && this.props.dispatch(disconnect());
|
||||
APP.conference.isJoined() && this.props.dispatch(hangup());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +375,7 @@ class Conference extends AbstractConference<IProps, any> {
|
||||
|
||||
const { dispatch, t } = this.props;
|
||||
|
||||
dispatch(connect());
|
||||
dispatch(init());
|
||||
|
||||
maybeShowSuboptimalExperienceNotification(dispatch, t);
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
|
||||
interface IProps {
|
||||
_message?: string;
|
||||
}
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
notice: {
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
zIndex: 3,
|
||||
marginTop: theme.spacing(2),
|
||||
transform: 'translateX(-50%)'
|
||||
},
|
||||
|
||||
const Notice = ({ _message }: IProps) => {
|
||||
if (!_message) {
|
||||
message: {
|
||||
backgroundColor: theme.palette.uiBackground,
|
||||
color: theme.palette.text01,
|
||||
padding: '3px',
|
||||
borderRadius: '5px'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const Notice = () => {
|
||||
const message = useSelector((state: IReduxState) => state['features/base/config'].noticeMessage);
|
||||
const { classes } = useStyles();
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className = 'notice'>
|
||||
<span className = 'notice__message' >
|
||||
{_message}
|
||||
<div className = { classes.notice }>
|
||||
<span className = { classes.message } >
|
||||
{message}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated
|
||||
* {@code Notice}'s props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _message: string,
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const {
|
||||
noticeMessage
|
||||
} = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_message: noticeMessage
|
||||
};
|
||||
}
|
||||
export default connect(_mapStateToProps)(Notice);
|
||||
export default Notice;
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../../app/types';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractDisplayNamePrompt}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Invoked to update the local participant's display name.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Function to be invoked after a successful display name change.
|
||||
*/
|
||||
onPostSubmit?: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract class for {@code DisplayNamePrompt}.
|
||||
*/
|
||||
export default class AbstractDisplayNamePrompt<S>
|
||||
extends Component<IProps, S> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this._onSetDisplayName = this._onSetDisplayName.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the local participant's display name. A
|
||||
* name must be entered for the action to dispatch.
|
||||
*
|
||||
* It returns a boolean to comply the Dialog behaviour:
|
||||
* {@code true} - the dialog should be closed.
|
||||
* {@code false} - the dialog should be left open.
|
||||
*
|
||||
* @param {string} displayName - The display name to save.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onSetDisplayName(displayName: string) {
|
||||
if (!displayName?.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { dispatch, onPostSubmit } = this.props;
|
||||
|
||||
// Store display name in settings
|
||||
dispatch(updateSettings({
|
||||
displayName
|
||||
}));
|
||||
|
||||
onPostSubmit?.();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,29 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import InputDialog from '../../../base/dialog/components/native/InputDialog';
|
||||
import AbstractDisplayNamePrompt from '../AbstractDisplayNamePrompt';
|
||||
import { onSetDisplayName } from '../../functions';
|
||||
import { IProps } from '../../types';
|
||||
|
||||
/**
|
||||
* Implements a component to render a display name prompt.
|
||||
*/
|
||||
class DisplayNamePrompt extends AbstractDisplayNamePrompt<any> {
|
||||
class DisplayNamePrompt extends Component<IProps> {
|
||||
_onSetDisplayName: (displayName: string) => boolean;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DisplayNamePrompt} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onSetDisplayName = onSetDisplayName(props.dispatch, props.onPostSubmit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../../base/i18n/functions';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import Input from '../../../base/ui/components/web/Input';
|
||||
import AbstractDisplayNamePrompt, { IProps } from '../AbstractDisplayNamePrompt';
|
||||
import { onSetDisplayName } from '../../functions';
|
||||
import { IProps } from '../../types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link DisplayNamePrompt}.
|
||||
@@ -23,7 +24,9 @@ interface IState {
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class DisplayNamePrompt extends AbstractDisplayNamePrompt<IState> {
|
||||
class DisplayNamePrompt extends Component<IProps, IState> {
|
||||
_onSetDisplayName: (displayName: string) => boolean;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DisplayNamePrompt} instance.
|
||||
*
|
||||
@@ -40,6 +43,7 @@ class DisplayNamePrompt extends AbstractDisplayNamePrompt<IState> {
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDisplayNameChange = this._onDisplayNameChange.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._onSetDisplayName = onSetDisplayName(props.dispatch, props.onPostSubmit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
|
||||
/**
|
||||
* Appends a suffix to the display name.
|
||||
@@ -10,3 +12,33 @@ export function appendSuffix(displayName: string, suffix = ''): string {
|
||||
return `${displayName || suffix}${
|
||||
displayName && suffix && displayName !== suffix ? ` (${suffix})` : ''}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the local participant's display name. A
|
||||
* name must be entered for the action to dispatch.
|
||||
*
|
||||
* It returns a boolean to comply the Dialog behaviour:
|
||||
* {@code true} - the dialog should be closed.
|
||||
* {@code false} - the dialog should be left open.
|
||||
*
|
||||
* @param {Function} dispatch - Redux dispatch function.
|
||||
* @param {Function} onPostSubmit - Function to be invoked after a successful display name change.
|
||||
* @param {string} displayName - The display name to save.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function onSetDisplayName(dispatch: IStore['dispatch'], onPostSubmit?: Function) {
|
||||
return function(displayName: string) {
|
||||
if (!displayName?.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store display name in settings
|
||||
dispatch(updateSettings({
|
||||
displayName
|
||||
}));
|
||||
|
||||
onPostSubmit?.();
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
20
react/features/display-name/types.ts
Normal file
20
react/features/display-name/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IStore } from '../app/types';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of
|
||||
* {@link AbstractDisplayNamePrompt}.
|
||||
*/
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Invoked to update the local participant's display name.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Function to be invoked after a successful display name change.
|
||||
*/
|
||||
onPostSubmit?: Function;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Custom e2ee labels.
|
||||
*/
|
||||
_e2eeLabels?: any;
|
||||
|
||||
/**
|
||||
* True if the label needs to be rendered, false otherwise.
|
||||
*/
|
||||
_showLabel?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props of this {@code Component}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
const { e2ee = {} } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_e2eeLabels: e2ee.labels,
|
||||
_showLabel: state['features/base/participants'].numberOfParticipantsDisabledE2EE === 0
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,26 @@
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { IconE2EE } from '../../base/icons/svg';
|
||||
import Label from '../../base/label/components/web/Label';
|
||||
import { COLORS } from '../../base/label/constants';
|
||||
import Tooltip from '../../base/tooltip/components/Tooltip';
|
||||
|
||||
import { IProps, _mapStateToProps } from './AbstractE2EELabel';
|
||||
export interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* Custom e2ee labels.
|
||||
*/
|
||||
_e2eeLabels?: any;
|
||||
|
||||
/**
|
||||
* True if the label needs to be rendered, false otherwise.
|
||||
*/
|
||||
_showLabel?: boolean;
|
||||
}
|
||||
|
||||
|
||||
const E2EELabel = ({ _e2eeLabels, _showLabel, t }: IProps) => {
|
||||
@@ -27,4 +40,20 @@ const E2EELabel = ({ _e2eeLabels, _showLabel, t }: IProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps (parts of) the redux state to the associated props of this {@code Component}.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @private
|
||||
* @returns {IProps}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState) {
|
||||
const { e2ee = {} } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_e2eeLabels: e2ee.labels,
|
||||
_showLabel: state['features/base/participants'].numberOfParticipantsDisabledE2EE === 0
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(E2EELabel));
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { createE2EEEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Switch from '../../base/ui/components/web/Switch';
|
||||
import { toggleE2EE } from '../actions';
|
||||
import { MAX_MODE } from '../constants';
|
||||
import { doesEveryoneSupportE2EE } from '../functions';
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The resource for the description, computed based on the maxMode and whether the switch is toggled or not.
|
||||
@@ -44,89 +44,53 @@ interface IProps extends WithTranslation {
|
||||
dispatch: IStore['dispatch'];
|
||||
}
|
||||
|
||||
interface IState {
|
||||
const useStyles = makeStyles()(() => {
|
||||
return {
|
||||
e2eeSection: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
|
||||
/**
|
||||
* True if the switch is toggled on.
|
||||
*/
|
||||
toggled: boolean;
|
||||
}
|
||||
description: {
|
||||
fontSize: '13px',
|
||||
margin: '15px 0'
|
||||
},
|
||||
|
||||
controlRow: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: '15px',
|
||||
|
||||
'& label': {
|
||||
fontSize: '14px',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Implements a React {@code Component} for displaying a security dialog section with a field
|
||||
* for setting the E2EE key.
|
||||
*
|
||||
* @augments Component
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
class E2EESection extends Component<IProps, IState> {
|
||||
/**
|
||||
* Implements React's {@link Component#getDerivedStateFromProps()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getDerivedStateFromProps(props: IProps, state: IState) {
|
||||
if (props._toggled !== state.toggled) {
|
||||
const E2EESection = ({
|
||||
_descriptionResource,
|
||||
_enabled,
|
||||
_e2eeLabels,
|
||||
_everyoneSupportE2EE,
|
||||
_toggled,
|
||||
dispatch
|
||||
}: IProps) => {
|
||||
const { classes } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const [ toggled, setToggled ] = useState(_toggled ?? false);
|
||||
|
||||
return {
|
||||
toggled: props._toggled
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
toggled: false
|
||||
};
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onToggle = this._onToggle.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { _descriptionResource, _enabled, _e2eeLabels, _everyoneSupportE2EE, t } = this.props;
|
||||
const { toggled } = this.state;
|
||||
const description = _e2eeLabels?.description || t(_descriptionResource ?? '');
|
||||
const label = _e2eeLabels?.label || t('dialog.e2eeLabel');
|
||||
const warning = _e2eeLabels?.warning || t('dialog.e2eeWarning');
|
||||
|
||||
return (
|
||||
<div id = 'e2ee-section'>
|
||||
<p
|
||||
aria-live = 'polite'
|
||||
className = 'description'
|
||||
id = 'e2ee-section-description'>
|
||||
{ description }
|
||||
{ !_everyoneSupportE2EE && <br /> }
|
||||
{ !_everyoneSupportE2EE && warning }
|
||||
</p>
|
||||
<div className = 'control-row'>
|
||||
<label htmlFor = 'e2ee-section-switch'>
|
||||
{ label }
|
||||
</label>
|
||||
<Switch
|
||||
checked = { toggled }
|
||||
disabled = { !_enabled }
|
||||
id = 'e2ee-section-switch'
|
||||
onChange = { this._onToggle } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
setToggled(_toggled);
|
||||
}, [ _toggled ]);
|
||||
|
||||
/**
|
||||
* Callback to be invoked when the user toggles E2EE on or off.
|
||||
@@ -134,17 +98,44 @@ class E2EESection extends Component<IProps, IState> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToggle() {
|
||||
const newValue = !this.state.toggled;
|
||||
const _onToggle = useCallback(() => {
|
||||
const newValue = !toggled;
|
||||
|
||||
this.setState({
|
||||
toggled: newValue
|
||||
});
|
||||
setToggled(newValue);
|
||||
|
||||
sendAnalytics(createE2EEEvent(`enabled.${String(newValue)}`));
|
||||
this.props.dispatch(toggleE2EE(newValue));
|
||||
}
|
||||
}
|
||||
dispatch(toggleE2EE(newValue));
|
||||
}, [ toggled ]);
|
||||
|
||||
const description = _e2eeLabels?.description || t(_descriptionResource ?? '');
|
||||
const label = _e2eeLabels?.label || t('dialog.e2eeLabel');
|
||||
const warning = _e2eeLabels?.warning || t('dialog.e2eeWarning');
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { classes.e2eeSection }
|
||||
id = 'e2ee-section'>
|
||||
<p
|
||||
aria-live = 'polite'
|
||||
className = { classes.description }
|
||||
id = 'e2ee-section-description'>
|
||||
{description}
|
||||
{!_everyoneSupportE2EE && <br />}
|
||||
{!_everyoneSupportE2EE && warning}
|
||||
</p>
|
||||
<div className = { classes.controlRow }>
|
||||
<label htmlFor = 'e2ee-section-switch'>
|
||||
{label}
|
||||
</label>
|
||||
<Switch
|
||||
checked = { toggled }
|
||||
disabled = { !_enabled }
|
||||
id = 'e2ee-section-switch'
|
||||
onChange = { _onToggle } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for this component.
|
||||
@@ -180,4 +171,4 @@ function mapStateToProps(state: IReduxState) {
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(E2EESection));
|
||||
export default connect(mapStateToProps)(E2EESection);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { Theme } from '@mui/material';
|
||||
import { ClassNameMap, withStyles } from '@mui/styles';
|
||||
import React, { Component } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { createFeedbackOpenEvent } from '../../analytics/AnalyticsEvents';
|
||||
import { sendAnalytics } from '../../analytics/functions';
|
||||
import { IReduxState, IStore } from '../../app/types';
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { IJitsiConference } from '../../base/conference/reducer';
|
||||
import { isMobileBrowser } from '../../base/environment/utils';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import Icon from '../../base/icons/components/Icon';
|
||||
import { IconFavorite, IconFavoriteSolid } from '../../base/icons/svg';
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
@@ -17,7 +15,7 @@ import Dialog from '../../base/ui/components/web/Dialog';
|
||||
import Input from '../../base/ui/components/web/Input';
|
||||
import { cancelFeedback, submitFeedback } from '../actions.web';
|
||||
|
||||
const styles = (theme: Theme) => {
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
dialog: {
|
||||
marginBottom: theme.spacing(1)
|
||||
@@ -68,7 +66,7 @@ const styles = (theme: Theme) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* The scores to display for selecting. The score is the index in the array and
|
||||
@@ -84,31 +82,10 @@ const SCORES = [
|
||||
|
||||
const ICON_SIZE = 32;
|
||||
|
||||
type Scrollable = {
|
||||
scroll: Function;
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link FeedbackDialog}.
|
||||
*/
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The cached feedback message, if any, that was set when closing a previous
|
||||
* instance of {@code FeedbackDialog}.
|
||||
*/
|
||||
_message: string;
|
||||
|
||||
/**
|
||||
* The cached feedback score, if any, that was set when closing a previous
|
||||
* instance of {@code FeedbackDialog}.
|
||||
*/
|
||||
_score: number;
|
||||
|
||||
/**
|
||||
* An object containing the CSS classes.
|
||||
*/
|
||||
classes: ClassNameMap<string>;
|
||||
interface IProps {
|
||||
|
||||
/**
|
||||
* The JitsiConference that is being rated. The conference is passed in
|
||||
@@ -117,236 +94,74 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
conference: IJitsiConference;
|
||||
|
||||
/**
|
||||
* Invoked to signal feedback submission or canceling.
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Callback invoked when {@code FeedbackDialog} is unmounted.
|
||||
*/
|
||||
onClose: Function;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link FeedbackDialog}.
|
||||
*/
|
||||
interface IState {
|
||||
|
||||
/**
|
||||
* The currently entered feedback message.
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* The score selection index which is currently being hovered. The value -1
|
||||
* is used as a sentinel value to match store behavior of using -1 for no
|
||||
* score having been selected.
|
||||
*/
|
||||
mousedOverScore: number;
|
||||
|
||||
/**
|
||||
* The currently selected score selection index. The score will not be 0
|
||||
* indexed so subtract one to map with SCORES.
|
||||
*/
|
||||
score: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A React {@code Component} for displaying a dialog to rate the current
|
||||
* conference quality, write a message describing the experience, and submit
|
||||
* the feedback.
|
||||
*
|
||||
* @augments Component
|
||||
* @param {IProps} props - Component's props.
|
||||
* @returns {JSX}
|
||||
*/
|
||||
class FeedbackDialog extends Component<IProps, IState> {
|
||||
const FeedbackDialog = ({ conference, onClose }: IProps) => {
|
||||
const { classes } = useStyles();
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const _message = useSelector((state: IReduxState) => state['features/feedback'].message);
|
||||
const _score = useSelector((state: IReduxState) => state['features/feedback'].score);
|
||||
|
||||
/**
|
||||
* The currently entered feedback message.
|
||||
*/
|
||||
const [ message, setMessage ] = useState(_message);
|
||||
|
||||
/**
|
||||
* The score selection index which is currently being hovered. The
|
||||
* value -1 is used as a sentinel value to match store behavior of
|
||||
* using -1 for no score having been selected.
|
||||
*/
|
||||
const [ mousedOverScore, setMousedOverScore ] = useState(-1);
|
||||
|
||||
/**
|
||||
* The currently selected score selection index. The score will not
|
||||
* be 0 indexed so subtract one to map with SCORES.
|
||||
*/
|
||||
const [ score, setScore ] = useState(_score > -1 ? _score - 1 : _score);
|
||||
|
||||
/**
|
||||
* An array of objects with click handlers for each of the scores listed in
|
||||
* the constant SCORES. This pattern is used for binding event handlers only
|
||||
* once for each score selection icon.
|
||||
*/
|
||||
_scoreClickConfigurations: Array<{
|
||||
_onClick: (e: React.MouseEvent) => void;
|
||||
_onKeyDown: (e: React.KeyboardEvent) => void;
|
||||
_onMouseOver: (e: React.MouseEvent) => void;
|
||||
}>;
|
||||
|
||||
_onScrollTop: (node: Scrollable | null) => void;
|
||||
|
||||
/**
|
||||
* Initializes a new {@code FeedbackDialog} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only React {@code Component} props with
|
||||
* which the new instance is to be initialized.
|
||||
*/
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const { _message, _score } = this.props;
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* The currently entered feedback message.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
message: _message,
|
||||
|
||||
/**
|
||||
* The score selection index which is currently being hovered. The
|
||||
* value -1 is used as a sentinel value to match store behavior of
|
||||
* using -1 for no score having been selected.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
mousedOverScore: -1,
|
||||
|
||||
/**
|
||||
* The currently selected score selection index. The score will not
|
||||
* be 0 indexed so subtract one to map with SCORES.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
score: _score > -1 ? _score - 1 : _score
|
||||
const scoreClickConfigurations = useRef(SCORES.map((textKey, index) => {
|
||||
return {
|
||||
_onClick: () => onScoreSelect(index),
|
||||
_onKeyDown: (e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
onScoreSelect(index);
|
||||
}
|
||||
},
|
||||
_onMouseOver: () => onScoreMouseOver(index)
|
||||
};
|
||||
}));
|
||||
|
||||
this._scoreClickConfigurations = SCORES.map((textKey, index) => {
|
||||
return {
|
||||
_onClick: () => this._onScoreSelect(index),
|
||||
_onKeyDown: (e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
this._onScoreSelect(index);
|
||||
}
|
||||
},
|
||||
_onMouseOver: () => this._onScoreMouseOver(index)
|
||||
};
|
||||
});
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onCancel = this._onCancel.bind(this);
|
||||
this._onMessageChange = this._onMessageChange.bind(this);
|
||||
this._onScoreContainerMouseLeave
|
||||
= this._onScoreContainerMouseLeave.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
|
||||
// On some mobile browsers opening Feedback dialog scrolls down the whole content because of the keyboard.
|
||||
// By scrolling to the top we prevent hiding the feedback stars so the user knows those exist.
|
||||
this._onScrollTop = (node: Scrollable | null) => {
|
||||
node?.scroll?.(0, 0);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an analytics event to notify feedback has been opened.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
useEffect(() => {
|
||||
sendAnalytics(createFeedbackOpenEvent());
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyFeedbackPromptDisplayed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the onClose callback, if defined, to notify of the close event.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.props.onClose) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { message, mousedOverScore, score } = this.state;
|
||||
const scoreToDisplayAsSelected
|
||||
= mousedOverScore > -1 ? mousedOverScore : score;
|
||||
|
||||
const { classes, t } = this.props;
|
||||
|
||||
const scoreIcons = this._scoreClickConfigurations.map(
|
||||
(config, index) => {
|
||||
const isFilled = index <= scoreToDisplayAsSelected;
|
||||
const activeClass = isFilled ? 'active' : '';
|
||||
const className
|
||||
= `${classes.starBtn} ${activeClass}`;
|
||||
|
||||
return (
|
||||
<span
|
||||
aria-label = { t(SCORES[index]) }
|
||||
className = { className }
|
||||
key = { index }
|
||||
onClick = { config._onClick }
|
||||
onKeyDown = { config._onKeyDown }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }
|
||||
{ ...(isMobileBrowser() ? {} : {
|
||||
onMouseOver: config._onMouseOver
|
||||
}) }>
|
||||
{ isFilled
|
||||
? <Icon
|
||||
size = { ICON_SIZE }
|
||||
src = { IconFavoriteSolid } />
|
||||
: <Icon
|
||||
size = { ICON_SIZE }
|
||||
src = { IconFavorite } /> }
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{
|
||||
translationKey: 'dialog.Submit'
|
||||
}}
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onSubmit }
|
||||
size = 'large'
|
||||
titleKey = 'feedback.rateExperience'>
|
||||
<div className = { classes.dialog }>
|
||||
<div className = { classes.rating }>
|
||||
<div
|
||||
className = { classes.stars }
|
||||
onMouseLeave = { this._onScoreContainerMouseLeave }>
|
||||
{ scoreIcons }
|
||||
</div>
|
||||
<div
|
||||
className = { classes.ratingLabel } >
|
||||
<p className = 'sr-only'>
|
||||
{ t('feedback.accessibilityLabel.yourChoice', {
|
||||
rating: t(SCORES[scoreToDisplayAsSelected])
|
||||
}) }
|
||||
</p>
|
||||
<p
|
||||
aria-hidden = { true }
|
||||
id = 'starLabel'>
|
||||
{ t(SCORES[scoreToDisplayAsSelected]) }
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className = { classes.details }>
|
||||
<Input
|
||||
id = 'feedbackTextArea'
|
||||
label = { t('feedback.detailsLabel') }
|
||||
onChange = { this._onMessageChange }
|
||||
textarea = { true }
|
||||
value = { message } />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
return () => {
|
||||
onClose?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Dispatches an action notifying feedback was not submitted. The submitted
|
||||
@@ -356,14 +171,13 @@ class FeedbackDialog extends Component<IProps, IState> {
|
||||
* @private
|
||||
* @returns {boolean} Returns true to close the dialog.
|
||||
*/
|
||||
_onCancel() {
|
||||
const { message, score } = this.state;
|
||||
const onCancel = useCallback(() => {
|
||||
const scoreToSubmit = score > -1 ? score + 1 : score;
|
||||
|
||||
this.props.dispatch(cancelFeedback(scoreToSubmit, message));
|
||||
dispatch(cancelFeedback(scoreToSubmit, message));
|
||||
|
||||
return true;
|
||||
}
|
||||
}, [ score, message ]);
|
||||
|
||||
/**
|
||||
* Updates the known entered feedback message.
|
||||
@@ -373,19 +187,19 @@ class FeedbackDialog extends Component<IProps, IState> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onMessageChange(newValue: string) {
|
||||
this.setState({ message: newValue });
|
||||
}
|
||||
const onMessageChange = useCallback((newValue: string) => {
|
||||
setMessage(newValue);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Updates the currently selected score.
|
||||
*
|
||||
* @param {number} score - The index of the selected score in SCORES.
|
||||
* @param {number} newScore - The index of the selected score in SCORES.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onScoreSelect(score: number) {
|
||||
this.setState({ score });
|
||||
function onScoreSelect(newScore: number) {
|
||||
setScore(newScore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -395,20 +209,20 @@ class FeedbackDialog extends Component<IProps, IState> {
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onScoreContainerMouseLeave() {
|
||||
this.setState({ mousedOverScore: -1 });
|
||||
}
|
||||
const onScoreContainerMouseLeave = useCallback(() => {
|
||||
setMousedOverScore(-1);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Updates the known state of the score icon currently behind hovered over.
|
||||
*
|
||||
* @param {number} mousedOverScore - The index of the SCORES value currently
|
||||
* @param {number} newMousedOverScore - The index of the SCORES value currently
|
||||
* being moused over.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onScoreMouseOver(mousedOverScore: number) {
|
||||
this.setState({ mousedOverScore });
|
||||
function onScoreMouseOver(newMousedOverScore: number) {
|
||||
setMousedOverScore(newMousedOverScore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -418,46 +232,89 @@ class FeedbackDialog extends Component<IProps, IState> {
|
||||
* @private
|
||||
* @returns {boolean} Returns true to close the dialog.
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { conference, dispatch } = this.props;
|
||||
const { message, score } = this.state;
|
||||
|
||||
const _onSubmit = useCallback(() => {
|
||||
const scoreToSubmit = score > -1 ? score + 1 : score;
|
||||
|
||||
dispatch(submitFeedback(scoreToSubmit, message, conference));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}, [ score, message, conference ]);
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated {@code FeedbackDialog}'s
|
||||
* props.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState) {
|
||||
const { message, score } = state['features/feedback'];
|
||||
const scoreToDisplayAsSelected
|
||||
= mousedOverScore > -1 ? mousedOverScore : score;
|
||||
|
||||
return {
|
||||
/**
|
||||
* The cached feedback message, if any, that was set when closing a
|
||||
* previous instance of {@code FeedbackDialog}.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
_message: message,
|
||||
const scoreIcons = scoreClickConfigurations.current.map(
|
||||
(config, index) => {
|
||||
const isFilled = index <= scoreToDisplayAsSelected;
|
||||
const activeClass = isFilled ? 'active' : '';
|
||||
const className
|
||||
= `${classes.starBtn} ${activeClass}`;
|
||||
|
||||
/**
|
||||
* The currently selected score selection index.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
_score: score
|
||||
};
|
||||
}
|
||||
return (
|
||||
<span
|
||||
aria-label = { t(SCORES[index]) }
|
||||
className = { className }
|
||||
key = { index }
|
||||
onClick = { config._onClick }
|
||||
onKeyDown = { config._onKeyDown }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }
|
||||
{ ...(isMobileBrowser() ? {} : {
|
||||
onMouseOver: config._onMouseOver
|
||||
}) }>
|
||||
{isFilled
|
||||
? <Icon
|
||||
size = { ICON_SIZE }
|
||||
src = { IconFavoriteSolid } />
|
||||
: <Icon
|
||||
size = { ICON_SIZE }
|
||||
src = { IconFavorite } />}
|
||||
</span>
|
||||
);
|
||||
});
|
||||
|
||||
export default withStyles(styles)(translate(connect(_mapStateToProps)(FeedbackDialog)));
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{
|
||||
translationKey: 'dialog.Submit'
|
||||
}}
|
||||
onCancel = { onCancel }
|
||||
onSubmit = { _onSubmit }
|
||||
size = 'large'
|
||||
titleKey = 'feedback.rateExperience'>
|
||||
<div className = { classes.dialog }>
|
||||
<div className = { classes.rating }>
|
||||
<div
|
||||
className = { classes.stars }
|
||||
onMouseLeave = { onScoreContainerMouseLeave }>
|
||||
{scoreIcons}
|
||||
</div>
|
||||
<div
|
||||
className = { classes.ratingLabel } >
|
||||
<p className = 'sr-only'>
|
||||
{t('feedback.accessibilityLabel.yourChoice', {
|
||||
rating: t(SCORES[scoreToDisplayAsSelected])
|
||||
})}
|
||||
</p>
|
||||
<p
|
||||
aria-hidden = { true }
|
||||
id = 'starLabel'>
|
||||
{t(SCORES[scoreToDisplayAsSelected])}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className = { classes.details }>
|
||||
<Input
|
||||
id = 'feedbackTextArea'
|
||||
label = { t('feedback.detailsLabel') }
|
||||
onChange = { onMessageChange }
|
||||
textarea = { true }
|
||||
value = { message } />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedbackDialog;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
import { getParticipantById, hasRaisedHand } from '../../base/participants/functions';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* True if the hand is raised for this participant.
|
||||
*/
|
||||
_raisedHand?: boolean;
|
||||
|
||||
/**
|
||||
* The participant id who we want to render the raised hand indicator
|
||||
* for.
|
||||
*/
|
||||
participantId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an abstract class for the RaisedHandIndicator component.
|
||||
*/
|
||||
export default abstract class AbstractRaisedHandIndicator<P extends IProps>
|
||||
extends Component<P> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._raisedHand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._renderIndicator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
* @returns {React$Element<*>}
|
||||
*/
|
||||
abstract _renderIndicator(): React.ReactElement;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const participant = getParticipantById(state, ownProps.participantId);
|
||||
|
||||
return {
|
||||
_raisedHand: hasRaisedHand(participant)
|
||||
};
|
||||
}
|
||||
@@ -1,23 +1,49 @@
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import { View, ViewStyle } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import { IconRaiseHand } from '../../../base/icons/svg';
|
||||
import { getParticipantById, hasRaisedHand } from '../../../base/participants/functions';
|
||||
import BaseIndicator from '../../../base/react/components/native/BaseIndicator';
|
||||
import AbstractRaisedHandIndicator, {
|
||||
IProps,
|
||||
_mapStateToProps
|
||||
} from '../AbstractRaisedHandIndicator';
|
||||
|
||||
import styles from './styles';
|
||||
|
||||
export interface IProps {
|
||||
|
||||
/**
|
||||
* True if the hand is raised for this participant.
|
||||
*/
|
||||
_raisedHand?: boolean;
|
||||
|
||||
/**
|
||||
* The participant id who we want to render the raised hand indicator
|
||||
* for.
|
||||
*/
|
||||
participantId: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thumbnail badge showing that the participant would like to speak.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
class RaisedHandIndicator extends Component<IProps> {
|
||||
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
if (!this.props._raisedHand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._renderIndicator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the platform specific indicator element.
|
||||
*
|
||||
@@ -34,4 +60,19 @@ class RaisedHandIndicator extends AbstractRaisedHandIndicator<IProps> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps part of the Redux state to the props of this component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @param {IProps} ownProps - The own props of the component.
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _mapStateToProps(state: IReduxState, ownProps: IProps) {
|
||||
const participant = getParticipantById(state, ownProps.participantId);
|
||||
|
||||
return {
|
||||
_raisedHand: hasRaisedHand(participant)
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(_mapStateToProps)(RaisedHandIndicator);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getFeatureFlag } from '../../base/flags/functions';
|
||||
import { getLocalParticipant } from '../../base/participants/functions';
|
||||
import { getFieldValue } from '../../base/react/functions';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
import { IMessage } from '../../chat/reducer';
|
||||
import { IMessage } from '../../chat/types';
|
||||
import { isDeviceStatusVisible } from '../../prejoin/functions';
|
||||
import { cancelKnocking, joinWithPassword, onSendMessage, setPasswordJoinFailed, startKnocking } from '../actions';
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/**
|
||||
* The type of redux action to set the AppState API change event listener.
|
||||
* The type of redux action used for app state subscription.
|
||||
*
|
||||
* {
|
||||
* type: _SET_APP_STATE_LISTENER,
|
||||
* listener: Function
|
||||
* type: _SET_APP_STATE_SUBSCRIPTION,
|
||||
* subscription: NativeEventSubscription
|
||||
* }
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
export const _SET_APP_STATE_LISTENER = '_SET_APP_STATE_LISTENER';
|
||||
export const _SET_APP_STATE_SUBSCRIPTION = '_SET_APP_STATE_SUBSCRIPTION';
|
||||
|
||||
/**
|
||||
* The type of redux action which signals that the app state has changed (in
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
import { APP_STATE_CHANGED, _SET_APP_STATE_LISTENER } from './actionTypes';
|
||||
import { NativeEventSubscription } from 'react-native';
|
||||
|
||||
import { APP_STATE_CHANGED, _SET_APP_STATE_SUBSCRIPTION } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Sets the listener to be used with React Native's AppState API.
|
||||
* Sets subscription for app state.
|
||||
*
|
||||
* @param {Function} listener - Function to be set as the change event listener.
|
||||
* @protected
|
||||
* @param {Function} subscription - Subscription for the native event.
|
||||
* @private
|
||||
* @returns {{
|
||||
* type: _SET_APP_STATE_LISTENER,
|
||||
* listener: Function
|
||||
* type: _SET_APP_STATE_SUBSCRIPTION,
|
||||
* subscription: NativeEventSubscription
|
||||
* }}
|
||||
*/
|
||||
export function _setAppStateListener(listener?: Function) {
|
||||
export function _setAppStateSubscription(subscription?: NativeEventSubscription) {
|
||||
return {
|
||||
type: _SET_APP_STATE_LISTENER,
|
||||
listener
|
||||
type: _SET_APP_STATE_SUBSCRIPTION,
|
||||
subscription
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,7 @@ import { IStore } from '../../app/types';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../base/app/actionTypes';
|
||||
import MiddlewareRegistry from '../../base/redux/MiddlewareRegistry';
|
||||
|
||||
import { _SET_APP_STATE_LISTENER } from './actionTypes';
|
||||
import {
|
||||
_setAppStateListener as _setAppStateListenerA,
|
||||
appStateChanged
|
||||
} from './actions';
|
||||
import { _setAppStateSubscription, appStateChanged } from './actions';
|
||||
|
||||
/**
|
||||
* Middleware that captures App lifetime actions and subscribes to application
|
||||
@@ -23,18 +19,16 @@ import {
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case _SET_APP_STATE_LISTENER:
|
||||
return _setAppStateListenerF(store, next, action);
|
||||
|
||||
case APP_WILL_MOUNT: {
|
||||
const { dispatch } = store;
|
||||
|
||||
dispatch(_setAppStateListenerA(_onAppStateChange.bind(undefined, dispatch)));
|
||||
_setAppStateListener(store, next, action, _onAppStateChange.bind(undefined, dispatch));
|
||||
break;
|
||||
}
|
||||
|
||||
case APP_WILL_UNMOUNT:
|
||||
store.dispatch(_setAppStateListenerA(undefined));
|
||||
_setAppStateListener(store, next, action, undefined);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,19 +59,16 @@ function _onAppStateChange(dispatch: IStore['dispatch'], appState: string) {
|
||||
* specified action to the specified store.
|
||||
* @param {Action} action - The redux action {@code _SET_IMMERSIVE_LISTENER}
|
||||
* which is being dispatched in the specified store.
|
||||
* @param {any} listener - Listener for app state status.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setAppStateListenerF({ getState }: IStore, next: Function, action: AnyAction) {
|
||||
// Remove the old AppState listener and add the new one.
|
||||
const { appStateListener: oldListener } = getState()['features/background'];
|
||||
function _setAppStateListener({ dispatch, getState }: IStore, next: Function, action: AnyAction, listener: any) {
|
||||
const { subscription } = getState()['features/background'];
|
||||
const result = next(action);
|
||||
const { appStateListener: newListener } = getState()['features/background'];
|
||||
|
||||
if (oldListener !== newListener) {
|
||||
oldListener && AppState.removeEventListener('change', oldListener);
|
||||
newListener && AppState.addEventListener('change', newListener);
|
||||
}
|
||||
subscription?.remove();
|
||||
listener && dispatch(_setAppStateSubscription(AppState.addEventListener('change', listener)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { AppStateStatus } from 'react-native';
|
||||
import { NativeEventSubscription } from 'react-native';
|
||||
|
||||
import ReducerRegistry from '../../base/redux/ReducerRegistry';
|
||||
|
||||
import {
|
||||
APP_STATE_CHANGED,
|
||||
_SET_APP_STATE_LISTENER
|
||||
} from './actionTypes';
|
||||
import { APP_STATE_CHANGED, _SET_APP_STATE_SUBSCRIPTION } from './actionTypes';
|
||||
|
||||
export interface IBackgroundState {
|
||||
appState: string;
|
||||
appStateListener?: (state: AppStateStatus) => void;
|
||||
subscription?: NativeEventSubscription;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,10 +18,11 @@ const DEFAULT_STATE = {
|
||||
|
||||
ReducerRegistry.register<IBackgroundState>('features/background', (state = DEFAULT_STATE, action): IBackgroundState => {
|
||||
switch (action.type) {
|
||||
case _SET_APP_STATE_LISTENER:
|
||||
|
||||
case _SET_APP_STATE_SUBSCRIPTION:
|
||||
return {
|
||||
...state,
|
||||
appStateListener: action.listener
|
||||
subscription: action.subscription
|
||||
};
|
||||
|
||||
case APP_STATE_CHANGED:
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IReduxState, IStore } from '../../../app/types';
|
||||
import DialInSummary from '../../../invite/components/dial-in-summary/native/DialInSummary';
|
||||
import Prejoin from '../../../prejoin/components/native/Prejoin';
|
||||
import UnsafeRoomWarning from '../../../prejoin/components/native/UnsafeRoomWarning';
|
||||
import { isUnsafeRoomWarningEnabled } from '../../../prejoin/functions';
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
import WelcomePage from '../../../welcome/components/WelcomePage';
|
||||
@@ -39,6 +40,11 @@ interface IProps {
|
||||
*/
|
||||
dispatch: IStore['dispatch'];
|
||||
|
||||
/**
|
||||
* Is unsafe room warning available?
|
||||
*/
|
||||
isUnsafeRoomWarningAvailable: boolean;
|
||||
|
||||
/**
|
||||
* Is welcome page available?
|
||||
*/
|
||||
@@ -46,7 +52,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
|
||||
const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: IProps) => {
|
||||
const RootNavigationContainer = ({ dispatch, isUnsafeRoomWarningAvailable, isWelcomePageAvailable }: IProps) => {
|
||||
const initialRouteName = isWelcomePageAvailable
|
||||
? screen.welcome.main : screen.connecting;
|
||||
const onReady = useCallback(() => {
|
||||
@@ -92,10 +98,13 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: IProps) =
|
||||
component = { Prejoin }
|
||||
name = { screen.preJoin }
|
||||
options = { preJoinScreenOptions } />
|
||||
<RootStack.Screen
|
||||
component = { UnsafeRoomWarning }
|
||||
name = { screen.unsafeRoomWarning }
|
||||
options = { unsafeMeetingScreenOptions } />
|
||||
{
|
||||
isUnsafeRoomWarningAvailable
|
||||
&& <RootStack.Screen
|
||||
component = { UnsafeRoomWarning }
|
||||
name = { screen.unsafeRoomWarning }
|
||||
options = { unsafeMeetingScreenOptions } />
|
||||
}
|
||||
<RootStack.Screen
|
||||
component = { ConferenceNavigationContainer }
|
||||
name = { screen.conference.root }
|
||||
@@ -113,6 +122,7 @@ const RootNavigationContainer = ({ dispatch, isWelcomePageAvailable }: IProps) =
|
||||
*/
|
||||
function mapStateToProps(state: IReduxState) {
|
||||
return {
|
||||
isUnsafeRoomWarningAvailable: isUnsafeRoomWarningEnabled(state),
|
||||
isWelcomePageAvailable: isWelcomePageEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ const externalAPIEnabled = isExternalAPIAvailable();
|
||||
|
||||
switch (type) {
|
||||
case READY_TO_CLOSE:
|
||||
rnSdkHandlers.onReadyToClose && rnSdkHandlers.onReadyToClose();
|
||||
rnSdkHandlers?.onReadyToClose && rnSdkHandlers?.onReadyToClose();
|
||||
break;
|
||||
case CONFERENCE_JOINED:
|
||||
rnSdkHandlers.onConferenceJoined && rnSdkHandlers.onConferenceJoined();
|
||||
rnSdkHandlers?.onConferenceJoined && rnSdkHandlers?.onConferenceJoined();
|
||||
break;
|
||||
case CONFERENCE_WILL_JOIN:
|
||||
rnSdkHandlers.onConferenceWillJoin && rnSdkHandlers.onConferenceWillJoin();
|
||||
rnSdkHandlers?.onConferenceWillJoin && rnSdkHandlers?.onConferenceWillJoin();
|
||||
break;
|
||||
case CONFERENCE_LEFT:
|
||||
// Props are torn down at this point, perhaps need to leave this one out
|
||||
@@ -38,7 +38,7 @@ const externalAPIEnabled = isExternalAPIAvailable();
|
||||
const { participant } = action;
|
||||
const participantInfo = participantToParticipantInfo(participant);
|
||||
|
||||
rnSdkHandlers.onParticipantJoined && rnSdkHandlers.onParticipantJoined(participantInfo);
|
||||
rnSdkHandlers?.onParticipantJoined && rnSdkHandlers?.onParticipantJoined(participantInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
14
react/features/prejoin/actions.any.ts
Normal file
14
react/features/prejoin/actions.any.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Action used to set the stance of the display name.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinDisplayNameRequired() {
|
||||
return {
|
||||
type: SET_PREJOIN_DISPLAY_NAME_REQUIRED
|
||||
};
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { IStore } from '../app/types';
|
||||
import { updateConfig } from '../base/config/actions';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
import { connect } from '../base/connection/actions';
|
||||
import { browser } from '../base/lib-jitsi-meet';
|
||||
import { createLocalTrack } from '../base/lib-jitsi-meet/functions';
|
||||
import { MEDIA_TYPE } from '../base/media/constants';
|
||||
@@ -31,7 +32,6 @@ import {
|
||||
SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
|
||||
SET_PRECALL_TEST_RESULTS,
|
||||
SET_PREJOIN_DEVICE_ERRORS,
|
||||
SET_PREJOIN_DISPLAY_NAME_REQUIRED,
|
||||
SET_PREJOIN_PAGE_VISIBILITY,
|
||||
SET_SKIP_PREJOIN_RELOAD
|
||||
} from './actionTypes';
|
||||
@@ -66,6 +66,8 @@ const STATUS_REQ_FREQUENCY = 2000;
|
||||
*/
|
||||
const STATUS_REQ_CAP = 45;
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Polls for status change after dial out.
|
||||
* Changes dialog message based on response, closes the dialog if there is an error,
|
||||
@@ -226,33 +228,37 @@ export function joinConference(options?: Object, ignoreJoiningInProgress = false
|
||||
dispatch(setJoiningInProgress(true));
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
let localTracks = getLocalTracks(state['features/base/tracks']);
|
||||
|
||||
options && dispatch(updateConfig(options));
|
||||
|
||||
// Do not signal audio/video tracks if the user joins muted.
|
||||
for (const track of localTracks) {
|
||||
// Always add the audio track on Safari because of a known issue where audio playout doesn't happen
|
||||
// if the user joins audio and video muted.
|
||||
if (track.muted
|
||||
&& !(browser.isWebKitBased() && track.jitsiTrack && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) {
|
||||
try {
|
||||
await dispatch(replaceLocalTrack(track.jitsiTrack, null));
|
||||
} catch (error) {
|
||||
logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`);
|
||||
dispatch(connect()).then(async () => {
|
||||
// TODO keep this here till we move tracks and conference management from
|
||||
// conference.js to react.
|
||||
const state = getState();
|
||||
let localTracks = getLocalTracks(state['features/base/tracks']);
|
||||
|
||||
// Do not signal audio/video tracks if the user joins muted.
|
||||
for (const track of localTracks) {
|
||||
// Always add the audio track on Safari because of a known issue where audio playout doesn't happen
|
||||
// if the user joins audio and video muted.
|
||||
if (track.muted && !(browser.isWebKitBased() && track.jitsiTrack
|
||||
&& track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)) {
|
||||
try {
|
||||
await dispatch(replaceLocalTrack(track.jitsiTrack, null));
|
||||
} catch (error) {
|
||||
logger.error(`Failed to replace local track (${track.jitsiTrack}) with null: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-fetch the local tracks after muted tracks have been removed above.
|
||||
// This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should not be used
|
||||
// anymore.
|
||||
localTracks = getLocalTracks(getState()['features/base/tracks']);
|
||||
// Re-fetch the local tracks after muted tracks have been removed above.
|
||||
// This is needed, because the tracks are effectively disposed by the replaceLocalTrack and should not be
|
||||
// used anymore.
|
||||
localTracks = getLocalTracks(getState()['features/base/tracks']);
|
||||
|
||||
const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
|
||||
const jitsiTracks = localTracks.map((t: any) => t.jitsiTrack);
|
||||
|
||||
APP.conference.prejoinStart(jitsiTracks);
|
||||
APP.conference.startConference(jitsiTracks).catch(logger.error);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -465,17 +471,6 @@ export function setDialOutCountry(value: Object) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the stance of the display name.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function setPrejoinDisplayNameRequired() {
|
||||
return {
|
||||
type: SET_PREJOIN_DISPLAY_NAME_REQUIRED
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action used to set the dial out number.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { IReduxState } from '../app/types';
|
||||
import { getRoomName } from '../base/conference/functions';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
|
||||
import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions.any';
|
||||
import { UNSAFE_ROOM_WARNING } from '../base/flags/constants';
|
||||
import { getFeatureFlag } from '../base/flags/functions';
|
||||
import { isAudioMuted, isVideoMutedByUser } from '../base/media/functions';
|
||||
import { getLobbyConfig } from '../lobby/functions';
|
||||
|
||||
|
||||
/**
|
||||
* Selector for the visibility of the 'join by phone' button.
|
||||
*
|
||||
@@ -169,3 +172,14 @@ export function shouldAutoKnock(state: IReduxState): boolean {
|
||||
|| autoKnock || (iAmRecorder && iAmSipGateway))
|
||||
&& !state['features/lobby'].knocking);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the unsafe room warning flag is enabled.
|
||||
*
|
||||
* @param {IReduxState} stateful - The state of the app.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isUnsafeRoomWarningEnabled(stateful: IReduxState): boolean {
|
||||
return Boolean(navigator.product === 'ReactNative'
|
||||
&& getFeatureFlag(stateful, UNSAFE_ROOM_WARNING, true));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user