mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-08 15:50:21 +00:00
Compare commits
20 Commits
4834
...
android-sd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18c6486c5b | ||
|
|
e4b34e1c89 | ||
|
|
0067f6b077 | ||
|
|
989044b3a9 | ||
|
|
a78ca5fcad | ||
|
|
1ad40de487 | ||
|
|
2c9078985f | ||
|
|
77ee4b13e1 | ||
|
|
a3a2ce3875 | ||
|
|
e0c77dcd95 | ||
|
|
e035d33fa9 | ||
|
|
11202595bd | ||
|
|
415670e24b | ||
|
|
05f3b4390d | ||
|
|
cff0a619f5 | ||
|
|
f7c0d4f1fe | ||
|
|
8fccb05519 | ||
|
|
b4155ab6d2 | ||
|
|
a1d3870634 | ||
|
|
07f16a7a51 |
@@ -26,4 +26,4 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=21.0.0
|
||||
sdkVersion=3.2.0
|
||||
sdkVersion=3.3.0
|
||||
|
||||
@@ -36,8 +36,17 @@ public class BroadcastAction {
|
||||
|
||||
for (String key : this.data.keySet()) {
|
||||
try {
|
||||
// TODO add support for different types of objects
|
||||
nativeMap.putString(key, this.data.get(key).toString());
|
||||
if (this.data.get(key) instanceof Boolean) {
|
||||
nativeMap.putBoolean(key, (Boolean) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof Integer) {
|
||||
nativeMap.putInt(key, (Integer) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof Double) {
|
||||
nativeMap.putDouble(key, (Double) this.data.get(key));
|
||||
} else if (this.data.get(key) instanceof String) {
|
||||
nativeMap.putString(key, (String) this.data.get(key));
|
||||
} else {
|
||||
throw new Exception("Unsupported extra data type");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
JitsiMeetLogger.w(TAG + " invalid extra data in event", e);
|
||||
}
|
||||
@@ -66,7 +75,8 @@ public class BroadcastAction {
|
||||
RETRIEVE_PARTICIPANTS_INFO("org.jitsi.meet.RETRIEVE_PARTICIPANTS_INFO"),
|
||||
OPEN_CHAT("org.jitsi.meet.OPEN_CHAT"),
|
||||
CLOSE_CHAT("org.jitsi.meet.CLOSE_CHAT"),
|
||||
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE");
|
||||
SEND_CHAT_MESSAGE("org.jitsi.meet.SEND_CHAT_MESSAGE"),
|
||||
SET_VIDEO_MUTED("org.jitsi.meet.SET_VIDEO_MUTED");
|
||||
|
||||
private final String action;
|
||||
|
||||
|
||||
@@ -85,7 +85,9 @@ public class BroadcastEvent {
|
||||
SCREEN_SHARE_TOGGLED("org.jitsi.meet.SCREEN_SHARE_TOGGLED"),
|
||||
PARTICIPANTS_INFO_RETRIEVED("org.jitsi.meet.PARTICIPANTS_INFO_RETRIEVED"),
|
||||
CHAT_MESSAGE_RECEIVED("org.jitsi.meet.CHAT_MESSAGE_RECEIVED"),
|
||||
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED");
|
||||
CHAT_TOGGLED("org.jitsi.meet.CHAT_TOGGLED"),
|
||||
VIDEO_MUTED_CHANGED("org.jitsi.meet.VIDEO_MUTED_CHANGED");
|
||||
|
||||
|
||||
private static final String CONFERENCE_WILL_JOIN_NAME = "CONFERENCE_WILL_JOIN";
|
||||
private static final String CONFERENCE_JOINED_NAME = "CONFERENCE_JOINED";
|
||||
@@ -98,6 +100,7 @@ public class BroadcastEvent {
|
||||
private static final String PARTICIPANTS_INFO_RETRIEVED_NAME = "PARTICIPANTS_INFO_RETRIEVED";
|
||||
private static final String CHAT_MESSAGE_RECEIVED_NAME = "CHAT_MESSAGE_RECEIVED";
|
||||
private static final String CHAT_TOGGLED_NAME = "CHAT_TOGGLED";
|
||||
private static final String VIDEO_MUTED_CHANGED_NAME = "VIDEO_MUTED_CHANGED";
|
||||
|
||||
private final String action;
|
||||
|
||||
@@ -142,6 +145,8 @@ public class BroadcastEvent {
|
||||
return CHAT_MESSAGE_RECEIVED;
|
||||
case CHAT_TOGGLED_NAME:
|
||||
return CHAT_TOGGLED;
|
||||
case VIDEO_MUTED_CHANGED_NAME:
|
||||
return VIDEO_MUTED_CHANGED;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -40,4 +40,10 @@ public class BroadcastIntentHelper {
|
||||
intent.putExtra("message", message);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent buildSetVideoMutedIntent(boolean muted) {
|
||||
Intent intent = new Intent(BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
intent.putExtra("muted", muted);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ class ExternalAPIModule
|
||||
constants.put("OPEN_CHAT", BroadcastAction.Type.OPEN_CHAT.getAction());
|
||||
constants.put("CLOSE_CHAT", BroadcastAction.Type.CLOSE_CHAT.getAction());
|
||||
constants.put("SEND_CHAT_MESSAGE", BroadcastAction.Type.SEND_CHAT_MESSAGE.getAction());
|
||||
constants.put("SET_VIDEO_MUTED", BroadcastAction.Type.SET_VIDEO_MUTED.getAction());
|
||||
|
||||
return constants;
|
||||
}
|
||||
|
||||
@@ -309,11 +309,6 @@ class ConferenceConnector {
|
||||
room.join();
|
||||
}, 5000);
|
||||
|
||||
const { password }
|
||||
= APP.store.getState()['features/base/conference'];
|
||||
|
||||
AuthHandler.requireAuth(room, password);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -378,7 +373,6 @@ class ConferenceConnector {
|
||||
if (this.reconnectTimeout !== null) {
|
||||
clearTimeout(this.reconnectTimeout);
|
||||
}
|
||||
AuthHandler.closeAuth();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2242,7 +2236,7 @@ export default {
|
||||
});
|
||||
|
||||
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
|
||||
AuthHandler.authenticate(room);
|
||||
AuthHandler.authenticateExternal(room);
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
|
||||
@@ -677,6 +677,9 @@ var config = {
|
||||
*/
|
||||
// dynamicBrandingUrl: '',
|
||||
|
||||
// Sets the background transparency level. '0' is fully transparent, '1' is opaque.
|
||||
// backgroundAlpha: 1,
|
||||
|
||||
// The URL of the moderated rooms microservice, if available. If it
|
||||
// is present, a link to the service will be rendered on the welcome page,
|
||||
// otherwise the app doesn't render it.
|
||||
|
||||
@@ -3,18 +3,21 @@
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import AuthHandler from './modules/UI/authentication/AuthHandler';
|
||||
import { redirectToTokenAuthService } from './modules/UI/authentication/AuthHandler';
|
||||
import { hideLoginDialog } from './react/features/authentication/actions.web';
|
||||
import { LoginDialog } from './react/features/authentication/components';
|
||||
import { isTokenAuthEnabled } from './react/features/authentication/functions';
|
||||
import {
|
||||
connectionEstablished,
|
||||
connectionFailed
|
||||
} from './react/features/base/connection/actions';
|
||||
import { openDialog } from './react/features/base/dialog/actions';
|
||||
import {
|
||||
isFatalJitsiConnectionError,
|
||||
JitsiConnectionErrors,
|
||||
JitsiConnectionEvents
|
||||
} from './react/features/base/lib-jitsi-meet';
|
||||
import { setPrejoinDisplayNameRequired } from './react/features/prejoin/actions';
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
@@ -80,7 +83,7 @@ function checkForAttachParametersAndConnect(id, password, connection) {
|
||||
* @returns {Promise<JitsiConnection>} connection if
|
||||
* everything is ok, else error.
|
||||
*/
|
||||
function connect(id, password, roomName) {
|
||||
export function connect(id, password, roomName) {
|
||||
const connectionConfig = Object.assign({}, config);
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
@@ -214,10 +217,39 @@ export function openConnection({ id, password, retry, roomName }) {
|
||||
const { jwt } = APP.store.getState()['features/base/jwt'];
|
||||
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED && !jwt) {
|
||||
return AuthHandler.requestAuth(roomName, connect);
|
||||
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 => {
|
||||
APP.store.dispatch(hideLoginDialog());
|
||||
resolve(connection);
|
||||
};
|
||||
|
||||
APP.store.dispatch(
|
||||
openDialog(LoginDialog, { onSuccess,
|
||||
roomName })
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,25 +1,64 @@
|
||||
.virtual-background-dialog{
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
.thumbnail{
|
||||
object-fit: cover;
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
.thumbnail-selected{
|
||||
object-fit: cover;
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border: 2px solid #a4b8d1;
|
||||
}
|
||||
.blur-selected{
|
||||
border: 2px solid #a4b8d1;
|
||||
}
|
||||
.virtual-background-none{
|
||||
.virtual-background-dialog {
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto auto auto auto auto auto;
|
||||
max-width: 370px;
|
||||
cursor: pointer;
|
||||
.thumbnail {
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.thumbnail:hover ~ .delete-image-icon {
|
||||
display: block;
|
||||
}
|
||||
.thumbnail-selected {
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border: 2px solid #a4b8d1;
|
||||
}
|
||||
.blur-selected {
|
||||
border-radius: 10px;
|
||||
border: 2px solid #a4b8d1;
|
||||
}
|
||||
.virtual-background-none {
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #a4b8d1;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.none-selected {
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
height: 34px;
|
||||
width: 34px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #a4b8d1;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
.file-upload-btn {
|
||||
display: none;
|
||||
}
|
||||
.custom-file-upload {
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
border-radius: 10px;
|
||||
@@ -27,18 +66,24 @@
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.none-selected{
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #a4b8d1;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-image-icon {
|
||||
position: absolute;
|
||||
display: none;
|
||||
left: 36;
|
||||
bottom: 36;
|
||||
}
|
||||
.delete-image-icon:hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.thumbnail-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loading-content-text{
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
/* eslint-disable no-unused-vars, no-var, max-len */
|
||||
/* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */
|
||||
|
||||
/**
|
||||
* !!!IMPORTANT!!!
|
||||
*
|
||||
* This file is considered deprecated. All options will eventually be moved to
|
||||
* config.js, and no new options should be added here.
|
||||
*/
|
||||
|
||||
var interfaceConfig = {
|
||||
APP_NAME: 'Jitsi Meet',
|
||||
AUDIO_LEVEL_PRIMARY_COLOR: 'rgba(255,255,255,0.4)',
|
||||
|
||||
@@ -131,6 +131,10 @@
|
||||
NSLog(@"%@%@", @"Chat toggled: ", data);
|
||||
}
|
||||
|
||||
- (void)videoMutedChanged:(NSDictionary *)data {
|
||||
NSLog(@"%@%@", @"Video muted changed: ", data[@"muted"]);
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)terminate {
|
||||
|
||||
@@ -26,5 +26,6 @@
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
- (void)sendSetVideoMuted:(BOOL)muted;
|
||||
|
||||
@end
|
||||
|
||||
@@ -26,6 +26,7 @@ static NSString * const retrieveParticipantsInfoAction = @"org.jitsi.meet.RETRIE
|
||||
static NSString * const openChatAction = @"org.jitsi.meet.OPEN_CHAT";
|
||||
static NSString * const closeChatAction = @"org.jitsi.meet.CLOSE_CHAT";
|
||||
static NSString * const sendChatMessageAction = @"org.jitsi.meet.SEND_CHAT_MESSAGE";
|
||||
static NSString * const setVideoMutedAction = @"org.jitsi.meet.SET_VIDEO_MUTED";
|
||||
|
||||
@implementation ExternalAPI
|
||||
|
||||
@@ -47,7 +48,8 @@ RCT_EXPORT_MODULE();
|
||||
@"RETRIEVE_PARTICIPANTS_INFO": retrieveParticipantsInfoAction,
|
||||
@"OPEN_CHAT": openChatAction,
|
||||
@"CLOSE_CHAT": closeChatAction,
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction
|
||||
@"SEND_CHAT_MESSAGE": sendChatMessageAction,
|
||||
@"SET_VIDEO_MUTED" : setVideoMutedAction
|
||||
};
|
||||
};
|
||||
|
||||
@@ -70,7 +72,8 @@ RCT_EXPORT_MODULE();
|
||||
retrieveParticipantsInfoAction,
|
||||
openChatAction,
|
||||
closeChatAction,
|
||||
sendChatMessageAction
|
||||
sendChatMessageAction,
|
||||
setVideoMutedAction
|
||||
];
|
||||
}
|
||||
|
||||
@@ -193,4 +196,11 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:sendChatMessageAction body:data];
|
||||
}
|
||||
|
||||
- (void)sendSetVideoMuted:(BOOL)muted {
|
||||
NSDictionary *data = @{ @"muted": [NSNumber numberWithBool:muted]};
|
||||
|
||||
[self sendEventWithName:setVideoMutedAction body:data];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.2.0</string>
|
||||
<string>3.3.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -44,5 +44,6 @@
|
||||
- (void)openChat:(NSString * _Nullable)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)setVideoMuted:(BOOL)muted;
|
||||
|
||||
@end
|
||||
|
||||
@@ -155,6 +155,11 @@ static void initializeViewsMap() {
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)setVideoMuted:(BOOL)muted {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendSetVideoMuted:muted];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
/**
|
||||
|
||||
@@ -104,4 +104,11 @@
|
||||
*/
|
||||
- (void)chatToggled:(NSDictionary *)data;
|
||||
|
||||
/**
|
||||
* Called when videoMuted state changed.
|
||||
*
|
||||
* The `data` dictionary contains a `muted` key with state of the videoMuted for the localParticipant.
|
||||
*/
|
||||
- (void)videoMutedChanged:(NSDictionary *)data;
|
||||
|
||||
@end
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"today": "Vandaag"
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Chat openen",
|
||||
"error": "Fout: uw bericht \"{{originalText}}\" is niet verzonden. Reden: {{error}}",
|
||||
"fieldPlaceHolder": "Typ hier uw bericht",
|
||||
"messagebox": "Typ een bericht",
|
||||
@@ -101,6 +102,8 @@
|
||||
"address": "Adres:",
|
||||
"bandwidth": "Geschatte bandbreedte:",
|
||||
"bitrate": "Bitrate:",
|
||||
"audio_ssrc": "Audio SSRC:",
|
||||
"codecs": "Codecs (A/V): ",
|
||||
"bridgeCount": "Aantal servers: ",
|
||||
"connectedTo": "Verbonden met:",
|
||||
"e2e_rtt": "E2E RTT:",
|
||||
@@ -125,9 +128,12 @@
|
||||
"remoteport": "Externe poort:",
|
||||
"remoteport_plural": "Externe poorten:",
|
||||
"resolution": "Resolutie:",
|
||||
"savelogs": "Logs opslaan",
|
||||
"participant_id": "Deelnemer id:",
|
||||
"status": "Verbinding:",
|
||||
"transport": "Transport:",
|
||||
"transport_plural": "Transporten:"
|
||||
"transport_plural": "Transporten:",
|
||||
"video_ssrc": "Video SSRC:"
|
||||
},
|
||||
"dateUtils": {
|
||||
"earlier": "Eerder",
|
||||
@@ -174,6 +180,7 @@
|
||||
"cameraNotFoundError": "Camera niet gevonden.",
|
||||
"cameraNotSendingData": "Er is geen toegang tot uw camera verkregen. Controleer of dit apparaat wordt gebruikt door een andere toepassing, selecteer een ander apparaat vanuit de instellingen of probeer de toepassing te herladen.",
|
||||
"cameraNotSendingDataTitle": "Geen toegang tot camera",
|
||||
"cameraTimeoutError": "Er heeft een camera timeout opgetreden.",
|
||||
"cameraPermissionDeniedError": "U hebt geen toestemming verleend om uw camera te gebruiken. U kunt wel deelnemen aan de vergadering, maar anderen kunnen u niet zien. Gebruik de cameraknop in de adresbalk om dit op te lossen.",
|
||||
"cameraUnknownError": "Kan de camera om een onbekende reden niet gebruiken.",
|
||||
"cameraUnsupportedResolutionError": "Uw camera ondersteunt de vereiste videoresolutie niet.",
|
||||
@@ -190,6 +197,7 @@
|
||||
"connectErrorWithMsg": "Oeps! Er is iets misgegaan en er kon geen verbinding met de vergadering worden gemaakt: {{msg}}",
|
||||
"connecting": "Verbinding maken",
|
||||
"contactSupport": "Contact opnemen met ondersteuning",
|
||||
"copied": "Gekopieerd",
|
||||
"copy": "Kopiëren",
|
||||
"dismiss": "Negeren",
|
||||
"displayNameRequired": "Hallo! Wat is uw naam?",
|
||||
@@ -232,6 +240,7 @@
|
||||
"micNotSendingDataTitle": "Uw microfoon is gedempt door uw systeeminstellingen",
|
||||
"micPermissionDeniedError": "U hebt geen toestemming verleend om uw microfoon te gebruiken. U kunt wel deelnemen aan de vergadering, maar anderen kunnen u niet horen. Gebruik de cameraknop in de adresbalk om dit op te lossen.",
|
||||
"micUnknownError": "Kan de microfoon om een onbekende reden niet gebruiken.",
|
||||
"micTimeoutError": "Kan de microfoon niet gebruiken vanwege een timeout fout.",
|
||||
"muteEveryoneElseDialog": "Eenmaal gedempt kunt u het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.",
|
||||
"muteEveryoneElseTitle": "Iedereen dempen behalve {{whom}}?",
|
||||
"muteEveryoneDialog": "Weet u zeker dat u iedereen wilt dempen? U kunt het dempen niet opheffen, maar zij kunnen dit wel ieder moment zelf doen.",
|
||||
@@ -242,6 +251,13 @@
|
||||
"muteParticipantButton": "Dempen",
|
||||
"muteParticipantDialog": "Weet u zeker dat u deze deelnemer wilt dempen? U kunt het dempen niet opheffen, maar deze deelnemer kan dit wel ieder moment zelf doen.",
|
||||
"muteParticipantTitle": "Deze deelnemer dempen?",
|
||||
"muteEveryoneElsesVideoDialog": "Als u de camera's uitzet kan u hem niet meer aanzetten, maar de andere deelnemers kunnen dit wel ieder moment zelf doen.",
|
||||
"muteEveryoneElsesVideoTitle": "De camera van iedereen behalve {{whom}} uitzetten?",
|
||||
"muteEveryonesVideoDialog": "Weet u zeker dat u iedereen zijn camera uit wilt zetten? Als u de camera's uitzet kan u hem niet meer aanzetten, maar de andere deelnemers kunnen dit wel ieder moment zelf doen.",
|
||||
"muteEveryonesVideoTitle": "Iedereen zijn camera uitzetten?",
|
||||
"muteParticipantsVideoButton": "Camera uitzetten",
|
||||
"muteParticipantsVideoTitle": "Camera van deze deelnemer uitzetten?",
|
||||
"muteParticipantsVideoBody": "Het is niet mogelijk voor u om de camera weer aan te zetten, de deelnemer kan de camera wel weer aanzetten.",
|
||||
"Ok": "OK",
|
||||
"passwordLabel": "De vergadering is vergrendeld door een deelnemer. Voer het $t(lockRoomPassword) in om deel te nemen.",
|
||||
"passwordNotSupported": "Instellen van een $t(lockRoomPassword) voor de vergadering wordt niet ondersteund.",
|
||||
@@ -279,6 +295,7 @@
|
||||
"sendPrivateMessageTitle": "Privé versturen?",
|
||||
"serviceUnavailable": "Service niet beschikbaar",
|
||||
"sessTerminated": "Gesprek beëindigd",
|
||||
"sessionRestarted": "Gesprek herstart door de server",
|
||||
"Share": "Delen",
|
||||
"shareVideoLinkError": "Geef een juiste YouTube-link op",
|
||||
"shareVideoTitle": "Een video delen",
|
||||
@@ -300,7 +317,9 @@
|
||||
"tokenAuthFailedTitle": "Authenticering mislukt",
|
||||
"transcribing": "Transcriberen",
|
||||
"unlockRoom": "$t(lockRoomPasswordUppercase) voor vergadering verwijderen",
|
||||
"user": "gebruiker",
|
||||
"userPassword": "gebruikerswachtwoord",
|
||||
"videoLink": "Video link",
|
||||
"WaitForHostMsg": "De vergadering <b>{{room}}</b> is nog niet gestart. Authenticeer uzelf als u de host bent. Anders wacht u tot de host aanwezig is.",
|
||||
"WaitForHostMsgWOk": "De vergadering <b>{{room}}</b> is nog niet gestart. Als u de host bent, drukt u op 'OK' om uzelf te authenticeren. Anders wacht u tot de host aanwezig is.",
|
||||
"WaitingForHost": "Wachten op de host...",
|
||||
@@ -316,6 +335,9 @@
|
||||
"e2ee": {
|
||||
"labelToolTip": "Audio- en Videocommunicatie in dit gesprek is eind-tot-eind-versleuteld"
|
||||
},
|
||||
"embedMeeting": {
|
||||
"title": "Deze vergadering embedden"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Gemiddeld",
|
||||
"bad": "Slecht",
|
||||
@@ -477,6 +499,8 @@
|
||||
"mutedTitle": "U bent gedempt!",
|
||||
"mutedRemotelyTitle": "U bent gedempt door {{participantDisplayName}}!",
|
||||
"mutedRemotelyDescription": "U kunt het dempen altijd opheffen wanneer u klaar bent om te spreken. Demp opnieuw wanneer u klaar bent, om ruis buiten de vergadering te houden.",
|
||||
"videoMutedRemotelyTitle": "Uw camera is uitgezet door {{participantDisplayName}}!",
|
||||
"videoMutedRemotelyDescription": "U kan hem ten alle tijden weer aanzetten.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) verwijderd door een andere deelnemer",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) ingesteld door een ander deelnemer",
|
||||
"raisedHand": "{{name}} zou graag willen spreken.",
|
||||
@@ -501,6 +525,7 @@
|
||||
"audioAndVideoError": "Audio- en videofout:",
|
||||
"audioOnlyError": "Audiofout:",
|
||||
"audioTrackError": "Kon audiotrack niet aanmaken.",
|
||||
"audioDeviceProblem": "Er is een probleem met uw microfoon",
|
||||
"callMe": "Bel me",
|
||||
"callMeAtNumber": "Bel me op dit nummer:",
|
||||
"configuringDevices": "Apparaten instellen...",
|
||||
@@ -746,7 +771,7 @@
|
||||
"profile": "Uw profiel bewerken",
|
||||
"raiseHand": "Uw hand opsteken / laten zakken",
|
||||
"raiseYourHand": "Uw hand opsteken",
|
||||
"security": "Beveiligingsoptions",
|
||||
"security": "Beveiligingsopties",
|
||||
"Settings": "Instellingen",
|
||||
"sharedvideo": "Een YouTube-video delen",
|
||||
"shareRoom": "Iemand uitnodigen",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"addContacts": "Пригласите других людей",
|
||||
"copyInvite": "Скопировать приглашение на встречу",
|
||||
"copyLink": "Скопировать ссылку на встречу",
|
||||
"copyStream": "Скопировать ссылку на прямую транасляцию",
|
||||
"copyStream": "Скопировать ссылку на прямую трансляцию",
|
||||
"countryNotSupported": "Эта страна пока не поддерживается.",
|
||||
"countryReminder": "Вызов не в США? Пожалуйста, убедитесь, что указали код страны!",
|
||||
"defaultEmail": "Ваш адрес электронной почты",
|
||||
@@ -472,7 +472,7 @@
|
||||
"knockingParticipantList": "Список ожидающих участников",
|
||||
"nameField": "Введите ваше имя",
|
||||
"notificationLobbyAccessDenied": "{{originParticipantName}} запретил присоединиться {{targetParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{originParticipantName}}разрешил присоединиться {{targetParticipantName}} ",
|
||||
"notificationLobbyAccessGranted": "{{originParticipantName}} разрешил присоединиться {{targetParticipantName}} ",
|
||||
"notificationLobbyDisabled": "Лобби отключено пользователем {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Лобби включено пользователем {{originParticipantName}}",
|
||||
"notificationTitle": "Лобби",
|
||||
|
||||
@@ -175,6 +175,7 @@
|
||||
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
|
||||
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
|
||||
"applicationWindow": "Application window",
|
||||
"authenticationRequired": "Authentication required",
|
||||
"Back": "Back",
|
||||
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
|
||||
"cameraNotFoundError": "Camera was not found.",
|
||||
@@ -227,6 +228,7 @@
|
||||
"lockRoom": "Add meeting $t(lockRoomPasswordUppercase)",
|
||||
"lockTitle": "Lock failed",
|
||||
"logoutQuestion": "Are you sure you want to logout and stop the conference?",
|
||||
"login": "Login",
|
||||
"logoutTitle": "Logout",
|
||||
"maxUsersLimitReached": "The limit for maximum number of participants has been reached. The conference is full. Please contact the meeting owner or try again later!",
|
||||
"maxUsersLimitReachedTitle": "Maximum participants limit reached",
|
||||
@@ -312,12 +314,13 @@
|
||||
"tokenAuthFailedTitle": "Authentication failed",
|
||||
"transcribing": "Transcribing",
|
||||
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
|
||||
"user": "user",
|
||||
"userPassword": "user password",
|
||||
"user": "User",
|
||||
"userIdentifier": "User identifier",
|
||||
"userPassword": "User password",
|
||||
"videoLink": "Video link",
|
||||
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitingForHost": "Waiting for the host ...",
|
||||
"WaitingForHostTitle": "Waiting for the host ...",
|
||||
"Yes": "Yes",
|
||||
"yourEntireScreen": "Your entire screen"
|
||||
},
|
||||
@@ -336,7 +339,10 @@
|
||||
"virtualBackground": {
|
||||
"title": "Backgrounds",
|
||||
"enableBlur": "Enable blur",
|
||||
"removeBackground": "Remove background"
|
||||
"removeBackground": "Remove background",
|
||||
"uploadImage": "Upload image",
|
||||
"pleaseWait": "Please wait...",
|
||||
"none": "None"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Average",
|
||||
@@ -816,6 +822,7 @@
|
||||
"tileViewToggle": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"videomute": "Start / Stop camera",
|
||||
"videoSettings": "Video settings",
|
||||
"selectBackground": "Select background"
|
||||
},
|
||||
"transcribing": {
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
pinParticipant,
|
||||
kickParticipant
|
||||
} from '../../react/features/base/participants';
|
||||
import { updateSettings } from '../../react/features/base/settings';
|
||||
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
||||
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
|
||||
import { openChat } from '../../react/features/chat/actions.web';
|
||||
import {
|
||||
@@ -39,7 +41,7 @@ import {
|
||||
import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
|
||||
import { RECORDING_TYPES } from '../../react/features/recording/constants';
|
||||
import { getActiveSession } from '../../react/features/recording/functions';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
import { toggleTileView, setTileView } from '../../react/features/video-layout';
|
||||
import { muteAllParticipants } from '../../react/features/video-menu/actions';
|
||||
import { setVideoQuality } from '../../react/features/video-quality';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
@@ -169,6 +171,19 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('film.strip.toggled'));
|
||||
APP.UI.toggleFilmstrip();
|
||||
},
|
||||
'toggle-camera': () => {
|
||||
if (!isToggleCameraEnabled(APP.store.getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(toggleCamera());
|
||||
},
|
||||
'toggle-camera-mirror': () => {
|
||||
const state = APP.store.getState();
|
||||
const { localFlipX: currentFlipX } = state['features/base/settings'];
|
||||
|
||||
APP.store.dispatch(updateSettings({ localFlipX: !currentFlipX }));
|
||||
},
|
||||
'toggle-chat': () => {
|
||||
sendAnalytics(createApiEvent('chat.toggled'));
|
||||
APP.UI.toggleChat();
|
||||
@@ -209,6 +224,9 @@ function initCommands() {
|
||||
|
||||
APP.store.dispatch(toggleTileView());
|
||||
},
|
||||
'set-tile-view': enabled => {
|
||||
APP.store.dispatch(setTileView(enabled));
|
||||
},
|
||||
'video-hangup': (showFeedbackDialog = true) => {
|
||||
sendAnalytics(createApiEvent('video.hangup'));
|
||||
APP.conference.hangup(showFeedbackDialog);
|
||||
@@ -1147,6 +1165,23 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that recording has started or stopped.
|
||||
*
|
||||
* @param {boolean} on - True if recording is on, false otherwise.
|
||||
* @param {string} mode - Stream or file.
|
||||
* @param {string} error - Error type or null if success.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyRecordingStatusChanged(on: boolean, mode: string, error?: string) {
|
||||
this._sendEvent({
|
||||
name: 'recording-status-changed',
|
||||
on,
|
||||
mode,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
4
modules/API/external/external_api.js
vendored
4
modules/API/external/external_api.js
vendored
@@ -44,12 +44,15 @@ const commands = {
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendTones: 'send-tones',
|
||||
setLargeVideoParticipant: 'set-large-video-participant',
|
||||
setTileView: 'set-tile-view',
|
||||
setVideoQuality: 'set-video-quality',
|
||||
startRecording: 'start-recording',
|
||||
stopRecording: 'stop-recording',
|
||||
subject: 'subject',
|
||||
submitFeedback: 'submit-feedback',
|
||||
toggleAudio: 'toggle-audio',
|
||||
toggleCamera: 'toggle-camera',
|
||||
toggleCameraMirror: 'toggle-camera-mirror',
|
||||
toggleChat: 'toggle-chat',
|
||||
toggleFilmStrip: 'toggle-film-strip',
|
||||
toggleRaiseHand: 'toggle-raise-hand',
|
||||
@@ -87,6 +90,7 @@ const events = {
|
||||
'password-required': 'passwordRequired',
|
||||
'proxy-connection-event': 'proxyConnectionEvent',
|
||||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
'recording-status-changed': 'recordingStatusChanged',
|
||||
'video-ready-to-close': 'readyToClose',
|
||||
'video-conference-joined': 'videoConferenceJoined',
|
||||
'video-conference-left': 'videoConferenceLeft',
|
||||
|
||||
@@ -7,6 +7,7 @@ import EventEmitter from 'events';
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import { isMobileBrowser } from '../../react/features/base/environment/utils';
|
||||
import { setColorAlpha } from '../../react/features/base/util';
|
||||
import { toggleChat } from '../../react/features/chat';
|
||||
import { setDocumentUrl } from '../../react/features/etherpad';
|
||||
import { setFilmstripVisible } from '../../react/features/filmstrip';
|
||||
@@ -129,6 +130,13 @@ UI.start = function() {
|
||||
$('body').addClass('mobile-browser');
|
||||
} else {
|
||||
$('body').addClass('desktop-browser');
|
||||
|
||||
if (config.backgroundAlpha !== undefined) {
|
||||
const backgroundColor = $('body').css('background-color');
|
||||
const alphaColor = setColorAlpha(backgroundColor, config.backgroundAlpha);
|
||||
|
||||
$('body').css('background-color', alphaColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
@@ -138,7 +146,6 @@ UI.start = function() {
|
||||
}
|
||||
|
||||
APP.store.dispatch(setToolboxEnabled(false));
|
||||
UI.messageHandler.enablePopups(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
/* global APP, config, JitsiMeetJS, Promise */
|
||||
// @flow
|
||||
|
||||
import Logger from 'jitsi-meet-logger';
|
||||
|
||||
import { openConnection } from '../../../connection';
|
||||
import { setJWT } from '../../../react/features/base/jwt';
|
||||
import {
|
||||
JitsiConnectionErrors
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
isTokenAuthEnabled,
|
||||
getTokenAuthUrl
|
||||
} from '../../../react/features/authentication/functions';
|
||||
import { setJWT } from '../../../react/features/base/jwt';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import LoginDialog from './LoginDialog';
|
||||
|
||||
let externalAuthWindow;
|
||||
declare var APP: Object;
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
let externalAuthWindow;
|
||||
let authRequiredDialog;
|
||||
|
||||
const isTokenAuthEnabled
|
||||
= typeof config.tokenAuthUrl === 'string' && config.tokenAuthUrl.length;
|
||||
const getTokenAuthUrl
|
||||
= JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null, config.tokenAuthUrl);
|
||||
|
||||
/**
|
||||
* Authenticate using external service or just focus
|
||||
@@ -29,6 +26,8 @@ const getTokenAuthUrl
|
||||
* @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();
|
||||
|
||||
@@ -37,8 +36,8 @@ function doExternalAuth(room, lockPassword) {
|
||||
if (room.isJoined()) {
|
||||
let getUrl;
|
||||
|
||||
if (isTokenAuthEnabled) {
|
||||
getUrl = Promise.resolve(getTokenAuthUrl(room.getName(), true));
|
||||
if (isTokenAuthEnabled(config)) {
|
||||
getUrl = Promise.resolve(getTokenAuthUrl(config)(room.getName(), true));
|
||||
initJWTTokenListener(room);
|
||||
} else {
|
||||
getUrl = room.getExternalAuthUrl(true);
|
||||
@@ -48,13 +47,13 @@ function doExternalAuth(room, lockPassword) {
|
||||
url,
|
||||
() => {
|
||||
externalAuthWindow = null;
|
||||
if (!isTokenAuthEnabled) {
|
||||
if (!isTokenAuthEnabled(config)) {
|
||||
room.join(lockPassword);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
} else if (isTokenAuthEnabled) {
|
||||
} else if (isTokenAuthEnabled(config)) {
|
||||
redirectToTokenAuthService(room.getName());
|
||||
} else {
|
||||
room.getExternalAuthUrl().then(UIUtil.redirect);
|
||||
@@ -67,10 +66,12 @@ function doExternalAuth(room, lockPassword) {
|
||||
* back with "?jwt={the JWT token}" query parameter added.
|
||||
* @param {string} [roomName] the name of the conference room.
|
||||
*/
|
||||
function redirectToTokenAuthService(roomName) {
|
||||
export function redirectToTokenAuthService(roomName: string) {
|
||||
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(roomName, false));
|
||||
UIUtil.redirect(getTokenAuthUrl(config)(roomName, false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,58 +158,15 @@ function initJWTTokenListener(room) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate on the server.
|
||||
* @param {JitsiConference} room
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
*/
|
||||
function doXmppAuth(room, lockPassword) {
|
||||
const loginDialog = LoginDialog.showAuthDialog(
|
||||
/* successCallback */ (id, password) => {
|
||||
room.authenticateAndUpgradeRole({
|
||||
id,
|
||||
password,
|
||||
roomPassword: lockPassword,
|
||||
|
||||
/** Called when the XMPP login succeeds. */
|
||||
onLoginSuccessful() {
|
||||
loginDialog.displayConnectionStatus(
|
||||
'connection.FETCH_SESSION_ID');
|
||||
}
|
||||
})
|
||||
.then(
|
||||
/* onFulfilled */ () => {
|
||||
loginDialog.displayConnectionStatus(
|
||||
'connection.GOT_SESSION_ID');
|
||||
loginDialog.close();
|
||||
},
|
||||
/* onRejected */ error => {
|
||||
logger.error('authenticateAndUpgradeRole failed', error);
|
||||
|
||||
const { authenticationError, connectionError } = error;
|
||||
|
||||
if (authenticationError) {
|
||||
loginDialog.displayError(
|
||||
'connection.GET_SESSION_ID_ERROR',
|
||||
{ msg: authenticationError });
|
||||
} else if (connectionError) {
|
||||
loginDialog.displayError(connectionError);
|
||||
}
|
||||
});
|
||||
},
|
||||
/* cancelCallback */ () => loginDialog.close());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (isTokenAuthEnabled || room.isExternalAuthEnabled()) {
|
||||
function authenticateExternal(room: Object, lockPassword: string) {
|
||||
const config = APP.store.getState()['features/base/config'];
|
||||
|
||||
if (isTokenAuthEnabled(config) || room.isExternalAuthEnabled()) {
|
||||
doExternalAuth(room, lockPassword);
|
||||
} else {
|
||||
doXmppAuth(room, lockPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +177,7 @@ function authenticate(room, lockPassword) {
|
||||
* @param {string} [lockPassword] password to use if the conference is locked
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function logout(room) {
|
||||
function logout(room: Object) {
|
||||
return new Promise(resolve => {
|
||||
room.room.moderator.logout(resolve);
|
||||
}).then(url => {
|
||||
@@ -232,83 +190,7 @@ function logout(room) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (authRequiredDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
authRequiredDialog = LoginDialog.showAuthRequiredDialog(
|
||||
room.getName(), authenticate.bind(null, room, lockPassword)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close auth-related dialogs if there are any.
|
||||
*/
|
||||
function closeAuth() {
|
||||
if (externalAuthWindow) {
|
||||
externalAuthWindow.close();
|
||||
externalAuthWindow = null;
|
||||
}
|
||||
|
||||
if (authRequiredDialog) {
|
||||
authRequiredDialog.close();
|
||||
authRequiredDialog = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function showXmppPasswordPrompt(roomName, connect) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const authDialog = LoginDialog.showAuthDialog(
|
||||
(id, password) => {
|
||||
connect(id, password, roomName).then(connection => {
|
||||
authDialog.close();
|
||||
resolve(connection);
|
||||
}, err => {
|
||||
if (err === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
authDialog.displayError(err);
|
||||
} else {
|
||||
authDialog.close();
|
||||
reject(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
|
||||
* @param {function(id, password, roomName)} [connect] function that returns
|
||||
* a Promise which resolves with JitsiConnection or fails with one of
|
||||
* JitsiConnectionErrors.
|
||||
* @returns {Promise<JitsiConnection>}
|
||||
*/
|
||||
function requestAuth(roomName, connect) {
|
||||
if (isTokenAuthEnabled) {
|
||||
// This Promise never resolves as user gets redirected to another URL
|
||||
return new Promise(() => redirectToTokenAuthService(roomName));
|
||||
}
|
||||
|
||||
return showXmppPasswordPrompt(roomName, connect);
|
||||
|
||||
}
|
||||
|
||||
export default {
|
||||
authenticate,
|
||||
requireAuth,
|
||||
requestAuth,
|
||||
closeAuth,
|
||||
authenticateExternal,
|
||||
logout
|
||||
};
|
||||
|
||||
@@ -212,45 +212,5 @@ export default {
|
||||
}
|
||||
|
||||
return dialog;
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a notification that authentication is required to create the
|
||||
* conference, so the local participant should authenticate or wait for a
|
||||
* host.
|
||||
*
|
||||
* @param {string} room - The name of the conference.
|
||||
* @param {function} onAuthNow - The callback to invoke if the local
|
||||
* participant wants to authenticate.
|
||||
* @returns dialog
|
||||
*/
|
||||
showAuthRequiredDialog(room, onAuthNow) {
|
||||
const msg = APP.translation.generateTranslationHTML(
|
||||
'[html]dialog.WaitForHostMsg',
|
||||
{ room }
|
||||
);
|
||||
const buttonTxt = APP.translation.generateTranslationHTML(
|
||||
'dialog.IamHost'
|
||||
);
|
||||
const buttons = [ {
|
||||
title: buttonTxt,
|
||||
value: 'authNow'
|
||||
} ];
|
||||
|
||||
return APP.UI.messageHandler.openDialog(
|
||||
'dialog.WaitingForHost',
|
||||
msg,
|
||||
true,
|
||||
buttons,
|
||||
(e, submitValue) => {
|
||||
// Do not close the dialog yet.
|
||||
e.preventDefault();
|
||||
|
||||
// Open login popup.
|
||||
if (submitValue === 'authNow') {
|
||||
onAuthNow();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,12 +12,6 @@ import {
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Flag for enabling/disabling popups.
|
||||
* @type {boolean}
|
||||
*/
|
||||
let popupEnabled = true;
|
||||
|
||||
/**
|
||||
* Currently displayed two button dialog.
|
||||
* @type {null}
|
||||
@@ -167,7 +161,7 @@ const messageHandler = {
|
||||
|
||||
let { classes } = options;
|
||||
|
||||
if (!popupEnabled || twoButtonDialog) {
|
||||
if (twoButtonDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -233,88 +227,6 @@ const messageHandler = {
|
||||
return $.prompt.getApi();
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows a message to the user with two buttons: first is given as a
|
||||
* parameter and the second is Cancel.
|
||||
*
|
||||
* @param titleKey the key for the title of the message
|
||||
* @param msgString the text of the message
|
||||
* @param persistent boolean value which determines whether the message is
|
||||
* persistent or not
|
||||
* @param buttons object with the buttons. The keys must be the name of the
|
||||
* button and value is the value that will be passed to
|
||||
* submitFunction
|
||||
* @param submitFunction function to be called on submit
|
||||
* @param loadedFunction function to be called after the prompt is fully
|
||||
* loaded
|
||||
* @param closeFunction function to be called on dialog close
|
||||
* @param {object} dontShowAgain - options for dont show again checkbox.
|
||||
* @param {string} dontShowAgain.id the id of the checkbox.
|
||||
* @param {string} dontShowAgain.textKey the key for the text displayed
|
||||
* next to checkbox
|
||||
* @param {boolean} dontShowAgain.checked if true the checkbox is foing to
|
||||
* be checked
|
||||
* @param {Array} dontShowAgain.buttonValues The button values that will
|
||||
* trigger storing the checkbox value
|
||||
* @param {string} dontShowAgain.localStorageKey the key for the local
|
||||
* storage. if not provided dontShowAgain.id will be used.
|
||||
*/
|
||||
openDialog(// eslint-disable-line max-params
|
||||
titleKey,
|
||||
msgString,
|
||||
persistent,
|
||||
buttons,
|
||||
submitFunction,
|
||||
loadedFunction,
|
||||
closeFunction,
|
||||
dontShowAgain) {
|
||||
if (!popupEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dontShowTheDialog(dontShowAgain)) {
|
||||
// Maybe we should pass some parameters here? I'm not sure
|
||||
// and currently we don't need any parameters.
|
||||
submitFunction();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const args = {
|
||||
title: this._getFormattedTitleString(titleKey),
|
||||
persistent,
|
||||
buttons,
|
||||
defaultButton: 1,
|
||||
promptspeed: 0,
|
||||
loaded() {
|
||||
if (loadedFunction) {
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
loadedFunction.apply(this, arguments);
|
||||
}
|
||||
|
||||
// Hide the close button
|
||||
if (persistent) {
|
||||
$('.jqiclose', this).hide();
|
||||
}
|
||||
},
|
||||
submit: dontShowAgainSubmitFunctionWrapper(
|
||||
dontShowAgain, submitFunction),
|
||||
close: closeFunction,
|
||||
classes: this._getDialogClasses()
|
||||
};
|
||||
|
||||
if (persistent) {
|
||||
args.closeText = '';
|
||||
}
|
||||
|
||||
const dialog = $.prompt(
|
||||
msgString + generateDontShowCheckbox(dontShowAgain), args);
|
||||
|
||||
APP.translation.translateElement(dialog);
|
||||
|
||||
return $.prompt.getApi();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the formatted title string.
|
||||
*
|
||||
@@ -358,9 +270,6 @@ const messageHandler = {
|
||||
* @param translateOptions options passed to translation
|
||||
*/
|
||||
openDialogWithStates(statesObject, options, translateOptions) {
|
||||
if (!popupEnabled) {
|
||||
return;
|
||||
}
|
||||
const { classes, size } = options;
|
||||
const defaultClasses = this._getDialogClasses(size);
|
||||
|
||||
@@ -397,10 +306,6 @@ const messageHandler = {
|
||||
*/
|
||||
// eslint-disable-next-line max-params
|
||||
openCenteredPopup(url, w, h, onPopupClosed) {
|
||||
if (!popupEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const l = window.screenX + (window.innerWidth / 2) - (w / 2);
|
||||
const t = window.screenY + (window.innerHeight / 2) - (h / 2);
|
||||
const popup = window.open(
|
||||
@@ -481,19 +386,6 @@ const messageHandler = {
|
||||
notify(titleKey, messageKey, messageArguments) {
|
||||
this.participantNotification(
|
||||
null, titleKey, null, messageKey, messageArguments);
|
||||
},
|
||||
|
||||
enablePopups(enable) {
|
||||
popupEnabled = enable;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if dialog is opened
|
||||
* false otherwise
|
||||
* @returns {boolean} isOpened
|
||||
*/
|
||||
isDialogOpened() {
|
||||
return Boolean($.prompt.getCurrentStateName());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
432
package-lock.json
generated
432
package-lock.json
generated
@@ -4708,6 +4708,135 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"array.prototype.map": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.3.tgz",
|
||||
"integrity": "sha512-nNcb30v0wfDyIe26Yif3PcV1JXQp4zEeEfupG7L4SRjnD6HLbO5b2a7eVSba53bOx4YCHYMBHt+Fp4vYstneRA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.18.0-next.1",
|
||||
"es-array-method-boxes-properly": "^1.0.0",
|
||||
"is-string": "^1.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
||||
"requires": {
|
||||
"object-keys": "^1.0.12"
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
|
||||
"integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"es-to-primitive": "^1.2.1",
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.2",
|
||||
"is-callable": "^1.2.3",
|
||||
"is-negative-zero": "^2.0.1",
|
||||
"is-regex": "^1.1.2",
|
||||
"is-string": "^1.0.5",
|
||||
"object-inspect": "^1.9.0",
|
||||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.2",
|
||||
"string.prototype.trimend": "^1.0.4",
|
||||
"string.prototype.trimstart": "^1.0.4",
|
||||
"unbox-primitive": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"es-to-primitive": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
||||
"requires": {
|
||||
"is-callable": "^1.1.4",
|
||||
"is-date-object": "^1.0.1",
|
||||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
|
||||
"integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ=="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
|
||||
"integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
|
||||
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
|
||||
"integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
|
||||
},
|
||||
"object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
||||
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"define-properties": "^1.1.3",
|
||||
"has-symbols": "^1.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimend": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
|
||||
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimstart": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
|
||||
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"arrify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
@@ -5566,6 +5695,15 @@
|
||||
"unset-value": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"caller-callsite": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz",
|
||||
@@ -7144,6 +7282,46 @@
|
||||
"is-regex": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"es-array-method-boxes-properly": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
|
||||
"integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA=="
|
||||
},
|
||||
"es-get-iterator": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz",
|
||||
"integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"get-intrinsic": "^1.1.0",
|
||||
"has-symbols": "^1.0.1",
|
||||
"is-arguments": "^1.1.0",
|
||||
"is-map": "^2.0.2",
|
||||
"is-set": "^2.0.2",
|
||||
"is-string": "^1.0.5",
|
||||
"isarray": "^2.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"is-arguments": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz",
|
||||
"integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
|
||||
"integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"es-to-primitive": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
|
||||
@@ -8802,6 +8980,31 @@
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
|
||||
"integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U="
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz",
|
||||
"integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"get-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||
@@ -8945,6 +9148,11 @@
|
||||
"function-bind": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"has-bigints": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz",
|
||||
"integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA=="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
|
||||
@@ -9570,6 +9778,11 @@
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
|
||||
},
|
||||
"is-bigint": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz",
|
||||
"integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg=="
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
@@ -9579,6 +9792,14 @@
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-boolean-object": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz",
|
||||
"integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-buffer": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
|
||||
@@ -9701,6 +9922,16 @@
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-map": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
|
||||
"integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg=="
|
||||
},
|
||||
"is-negative-zero": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz",
|
||||
"integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w=="
|
||||
},
|
||||
"is-number": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
@@ -9709,6 +9940,11 @@
|
||||
"kind-of": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"is-number-object": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
|
||||
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw=="
|
||||
},
|
||||
"is-obj": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
|
||||
@@ -9765,11 +10001,21 @@
|
||||
"integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==",
|
||||
"dev": true
|
||||
},
|
||||
"is-set": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz",
|
||||
"integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g=="
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||
},
|
||||
"is-string": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
|
||||
"integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ=="
|
||||
},
|
||||
"is-svg": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz",
|
||||
@@ -9823,6 +10069,20 @@
|
||||
"whatwg-fetch": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"iterate-iterator": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz",
|
||||
"integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw=="
|
||||
},
|
||||
"iterate-value": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz",
|
||||
"integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==",
|
||||
"requires": {
|
||||
"es-get-iterator": "^1.0.2",
|
||||
"iterate-iterator": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"jQuery-Impromptu": {
|
||||
"version": "github:trentrichardson/jQuery-Impromptu#753c2833f62f9c00301dd8b75af03599dc4f2ee8",
|
||||
"from": "github:trentrichardson/jQuery-Impromptu#v6.0.0"
|
||||
@@ -10253,8 +10513,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#4191198233ae64e3cb12349069c582724d09537c",
|
||||
"from": "github:jitsi/lib-jitsi-meet#4191198233ae64e3cb12349069c582724d09537c",
|
||||
"version": "github:jitsi/lib-jitsi-meet#0e180efdfa46a048ae1276cfbdf9cf8e051405c7",
|
||||
"from": "github:jitsi/lib-jitsi-meet#0e180efdfa46a048ae1276cfbdf9cf8e051405c7",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
@@ -13199,6 +13459,136 @@
|
||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
||||
"dev": true
|
||||
},
|
||||
"promise.allsettled": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.4.tgz",
|
||||
"integrity": "sha512-o73CbvQh/OnPFShxHcHxk0baXR2a1m4ozb85ha0H14VEoi/EJJLa9mnPfEWJx9RjA9MLfhdjZ8I6HhWtBa64Ag==",
|
||||
"requires": {
|
||||
"array.prototype.map": "^1.0.3",
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3",
|
||||
"es-abstract": "^1.18.0-next.2",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"iterate-value": "^1.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
|
||||
"requires": {
|
||||
"object-keys": "^1.0.12"
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0.tgz",
|
||||
"integrity": "sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"es-to-primitive": "^1.2.1",
|
||||
"function-bind": "^1.1.1",
|
||||
"get-intrinsic": "^1.1.1",
|
||||
"has": "^1.0.3",
|
||||
"has-symbols": "^1.0.2",
|
||||
"is-callable": "^1.2.3",
|
||||
"is-negative-zero": "^2.0.1",
|
||||
"is-regex": "^1.1.2",
|
||||
"is-string": "^1.0.5",
|
||||
"object-inspect": "^1.9.0",
|
||||
"object-keys": "^1.1.1",
|
||||
"object.assign": "^4.1.2",
|
||||
"string.prototype.trimend": "^1.0.4",
|
||||
"string.prototype.trimstart": "^1.0.4",
|
||||
"unbox-primitive": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"es-to-primitive": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
|
||||
"integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
|
||||
"requires": {
|
||||
"is-callable": "^1.1.4",
|
||||
"is-date-object": "^1.0.1",
|
||||
"is-symbol": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"is-callable": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
|
||||
"integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ=="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz",
|
||||
"integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
|
||||
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz",
|
||||
"integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw=="
|
||||
},
|
||||
"object-keys": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
|
||||
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz",
|
||||
"integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"define-properties": "^1.1.3",
|
||||
"has-symbols": "^1.0.1",
|
||||
"object-keys": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimend": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz",
|
||||
"integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"string.prototype.trimstart": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz",
|
||||
"integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
@@ -16674,6 +17064,17 @@
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
|
||||
"integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po="
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-P/51NX+JXyxK/aigg1/ZgyccdAxm5K1+n8+tvqSntjOivPt19gvm1VC49RWYetsiub8WViUchdxl/KWHHB0kzA==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1",
|
||||
"has-bigints": "^1.0.0",
|
||||
"has-symbols": "^1.0.0",
|
||||
"which-boxed-primitive": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"unicode-canonical-property-names-ecmascript": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
|
||||
@@ -18444,6 +18845,33 @@
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"which-boxed-primitive": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
|
||||
"integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
|
||||
"requires": {
|
||||
"is-bigint": "^1.0.1",
|
||||
"is-boolean-object": "^1.1.0",
|
||||
"is-number-object": "^1.0.4",
|
||||
"is-string": "^1.0.5",
|
||||
"is-symbol": "^1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-symbols": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz",
|
||||
"integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw=="
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
|
||||
"integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
|
||||
"requires": {
|
||||
"has-symbols": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
|
||||
@@ -48,19 +48,20 @@
|
||||
"i18next": "17.0.6",
|
||||
"i18next-browser-languagedetector": "3.0.1",
|
||||
"i18next-xhr-backend": "3.0.0",
|
||||
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"jitsi-meet-logger": "github:jitsi/jitsi-meet-logger#v1.0.0",
|
||||
"jquery": "3.5.1",
|
||||
"jquery-i18next": "1.2.1",
|
||||
"jQuery-Impromptu": "github:trentrichardson/jQuery-Impromptu#v6.0.0",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#4191198233ae64e3cb12349069c582724d09537c",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0e180efdfa46a048ae1276cfbdf9cf8e051405c7",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"pixelmatch": "5.1.0",
|
||||
"promise.allsettled": "1.0.4",
|
||||
"punycode": "2.1.1",
|
||||
"react": "16.12",
|
||||
"react-dom": "16.12",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @flow
|
||||
|
||||
import '../authentication/middleware';
|
||||
import '../base/devices/middleware';
|
||||
import '../e2ee/middleware';
|
||||
import '../external-api/middleware';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import '../analytics/reducer';
|
||||
import '../authentication/reducer';
|
||||
import '../base/app/reducer';
|
||||
import '../base/audio-only/reducer';
|
||||
import '../base/color-scheme/reducer';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import '../authentication/reducer';
|
||||
import '../mobile/audio-mode/reducer';
|
||||
import '../mobile/background/reducer';
|
||||
import '../mobile/call-integration/reducer';
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { appNavigate } from '../app/actions';
|
||||
import { checkIfCanJoin, conferenceLeft } from '../base/conference';
|
||||
import { connectionFailed } from '../base/connection';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { checkIfCanJoin, conferenceLeft } from '../base/conference/actions';
|
||||
import { connectionFailed } from '../base/connection/actions.native';
|
||||
import { openDialog } from '../base/dialog/actions';
|
||||
import { set } from '../base/redux';
|
||||
|
||||
import {
|
||||
67
react/features/authentication/actions.web.js
Normal file
67
react/features/authentication/actions.web.js
Normal file
@@ -0,0 +1,67 @@
|
||||
// @flow
|
||||
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions';
|
||||
import { hideDialog, openDialog } from '../base/dialog/actions';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN
|
||||
} from './actionTypes';
|
||||
import { WaitForOwnerDialog, LoginDialog } from './components';
|
||||
|
||||
/**
|
||||
* Cancels {@ink LoginDialog}.
|
||||
*
|
||||
* @returns {{
|
||||
* type: CANCEL_LOGIN
|
||||
* }}
|
||||
*/
|
||||
export function cancelLogin() {
|
||||
return {
|
||||
type: CANCEL_LOGIN
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels authentication, closes {@link WaitForOwnerDialog}
|
||||
* and navigates back to the welcome page.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function cancelWaitForOwner() {
|
||||
return (dispatch: Function) => {
|
||||
dispatch(maybeRedirectToWelcomePage());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides a authentication dialog where the local participant
|
||||
* should authenticate.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function hideLoginDialog() {
|
||||
return hideDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a authentication dialog where the local participant
|
||||
* should authenticate.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function openLoginDialog() {
|
||||
return openDialog(LoginDialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification dialog that authentication is required to create the.
|
||||
* Conference, so the local participant should authenticate or wait for a
|
||||
* host.
|
||||
*
|
||||
* @returns {Function}.
|
||||
*/
|
||||
export function openWaitForOwnerDialog() {
|
||||
return openDialog(WaitForOwnerDialog);
|
||||
}
|
||||
|
||||
|
||||
1
react/features/authentication/components/_.native.js
Normal file
1
react/features/authentication/components/_.native.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './native';
|
||||
1
react/features/authentication/components/_.web.js
Normal file
1
react/features/authentication/components/_.web.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './web';
|
||||
@@ -1,2 +1 @@
|
||||
export { default as LoginDialog } from './LoginDialog';
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
||||
export * from './_';
|
||||
|
||||
@@ -5,20 +5,20 @@ import { Text, TextInput, View } from 'react-native';
|
||||
import { connect as reduxConnect } from 'react-redux';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../base/color-scheme';
|
||||
import { toJid } from '../../base/connection';
|
||||
import { connect } from '../../base/connection/actions.native';
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
import { toJid } from '../../../base/connection';
|
||||
import { connect } from '../../../base/connection/actions.native';
|
||||
import {
|
||||
CustomSubmitDialog,
|
||||
FIELD_UNDERLINE,
|
||||
PLACEHOLDER_COLOR,
|
||||
_abstractMapStateToProps,
|
||||
inputDialog as inputDialogStyle
|
||||
} from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../base/lib-jitsi-meet';
|
||||
import type { StyleType } from '../../base/styles';
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../actions';
|
||||
} from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import type { StyleType } from '../../../base/styles';
|
||||
import { authenticateAndUpgradeRole, cancelLogin } from '../../actions.native';
|
||||
|
||||
// Register styles.
|
||||
import './styles';
|
||||
@@ -3,10 +3,10 @@
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { ConfirmDialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { cancelWaitForOwner, _openLoginDialog } from '../actions';
|
||||
import { ConfirmDialog } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { cancelWaitForOwner, _openLoginDialog } from '../../actions.native';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
@@ -107,9 +107,7 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _room: string
|
||||
* }}
|
||||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state) {
|
||||
const { authRequired } = state['features/base/conference'];
|
||||
2
react/features/authentication/components/native/index.js
Normal file
2
react/features/authentication/components/native/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as LoginDialog } from './LoginDialog';
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../base/color-scheme';
|
||||
import { BoxModel } from '../../base/styles';
|
||||
import { ColorSchemeRegistry, schemeColor } from '../../../base/color-scheme';
|
||||
import { BoxModel } from '../../../base/styles';
|
||||
|
||||
/**
|
||||
* The styles of the authentication feature.
|
||||
313
react/features/authentication/components/web/LoginDialog.js
Normal file
313
react/features/authentication/components/web/LoginDialog.js
Normal file
@@ -0,0 +1,313 @@
|
||||
// @flow
|
||||
|
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { connect } from '../../../../../connection';
|
||||
import { toJid } from '../../../base/connection/functions';
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate, translateToHTML } from '../../../base/i18n';
|
||||
import { JitsiConnectionErrors } from '../../../base/lib-jitsi-meet';
|
||||
import { connect as reduxConnect } from '../../../base/redux';
|
||||
import { authenticateAndUpgradeRole } from '../../actions.native';
|
||||
import { cancelLogin } from '../../actions.web';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link LoginDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* {@link JitsiConference} that needs authentication - will hold a valid
|
||||
* value in XMPP login + guest access mode.
|
||||
*/
|
||||
_conference: Object,
|
||||
|
||||
/**
|
||||
* The server hosts specified in the global config.
|
||||
*/
|
||||
_configHosts: Object,
|
||||
|
||||
/**
|
||||
* Indicates if the dialog should display "connecting" status message.
|
||||
*/
|
||||
_connecting: boolean,
|
||||
|
||||
/**
|
||||
* The error which occurred during login/authentication.
|
||||
*/
|
||||
_error: Object,
|
||||
|
||||
/**
|
||||
* The progress in the floating range between 0 and 1 of the authenticating
|
||||
* and upgrading the role of the local participant/user.
|
||||
*/
|
||||
_progress: number,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Invoked when username and password are submitted.
|
||||
*/
|
||||
onSuccess: Function,
|
||||
|
||||
/**
|
||||
* Conference room name.
|
||||
*/
|
||||
roomName: string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} state of {@link LoginDialog}.
|
||||
*/
|
||||
type State = {
|
||||
|
||||
/**
|
||||
* The user entered password for the conference.
|
||||
*/
|
||||
password: string,
|
||||
|
||||
/**
|
||||
* The user entered local participant name.
|
||||
*/
|
||||
username: string,
|
||||
|
||||
/**
|
||||
* Authentication process starts before joining the conference room.
|
||||
*/
|
||||
loginStarted: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Component that renders the login in conference dialog.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
class LoginDialog extends Component<Props, State> {
|
||||
/**
|
||||
* Initializes a new {@code LoginDialog} instance.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
username: '',
|
||||
password: '',
|
||||
loginStarted: false
|
||||
};
|
||||
|
||||
this._onCancelLogin = this._onCancelLogin.bind(this);
|
||||
this._onLogin = this._onLogin.bind(this);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
}
|
||||
|
||||
_onCancelLogin: () => void;
|
||||
|
||||
/**
|
||||
* Called when the cancel button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancelLogin() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(cancelLogin());
|
||||
}
|
||||
|
||||
_onLogin: () => void;
|
||||
|
||||
/**
|
||||
* Notifies this LoginDialog that the login button (OK) has been pressed by
|
||||
* the user.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onLogin() {
|
||||
const {
|
||||
_conference: conference,
|
||||
_configHosts: configHosts,
|
||||
roomName,
|
||||
onSuccess,
|
||||
dispatch
|
||||
} = this.props;
|
||||
const { password, username } = this.state;
|
||||
const jid = toJid(username, configHosts);
|
||||
|
||||
if (conference) {
|
||||
dispatch(authenticateAndUpgradeRole(jid, password, conference));
|
||||
} else {
|
||||
this.setState({
|
||||
loginStarted: true
|
||||
});
|
||||
|
||||
connect(jid, password, roomName)
|
||||
.then(connection => {
|
||||
onSuccess && onSuccess(connection);
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState({
|
||||
loginStarted: false
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onChange: Object => void;
|
||||
|
||||
/**
|
||||
* Callback for the onChange event of the field.
|
||||
*
|
||||
* @param {Object} evt - The static event.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChange(evt: Object) {
|
||||
this.setState({
|
||||
[evt.target.name]: evt.target.value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an optional message, if applicable.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
* @private
|
||||
*/
|
||||
renderMessage() {
|
||||
const {
|
||||
_configHosts: configHosts,
|
||||
_connecting: connecting,
|
||||
_error: error,
|
||||
_progress: progress,
|
||||
t
|
||||
} = this.props;
|
||||
const { username, password } = this.state;
|
||||
const messageOptions = {};
|
||||
let messageKey;
|
||||
|
||||
if (progress && progress >= 0.5) {
|
||||
messageKey = t('connection.FETCH_SESSION_ID');
|
||||
} else if (error) {
|
||||
const { name } = error;
|
||||
|
||||
if (name === JitsiConnectionErrors.PASSWORD_REQUIRED) {
|
||||
const { credentials } = error;
|
||||
|
||||
if (credentials
|
||||
&& credentials.jid === toJid(username, configHosts)
|
||||
&& credentials.password === password) {
|
||||
messageKey = t('dialog.incorrectPassword');
|
||||
}
|
||||
} else if (name) {
|
||||
messageKey = t('dialog.connectErrorWithMsg');
|
||||
messageOptions.msg = `${name} ${error.message}`;
|
||||
}
|
||||
} else if (connecting) {
|
||||
messageKey = t('connection.CONNECTING');
|
||||
}
|
||||
|
||||
if (messageKey) {
|
||||
return (
|
||||
<span>
|
||||
{ translateToHTML(t, messageKey, messageOptions) }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements {@Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_connecting: connecting,
|
||||
t
|
||||
} = this.props;
|
||||
const { password, loginStarted, username } = this.state;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okDisabled = {
|
||||
connecting
|
||||
|| loginStarted
|
||||
|| !password
|
||||
|| !username
|
||||
}
|
||||
okKey = { t('dialog.login') }
|
||||
onCancel = { this._onCancelLogin }
|
||||
onSubmit = { this._onLogin }
|
||||
titleKey = { t('dialog.authenticationRequired') }
|
||||
width = { 'small' }>
|
||||
<TextField
|
||||
autoFocus = { true }
|
||||
className = 'input-control'
|
||||
compact = { false }
|
||||
label = { t('dialog.user') }
|
||||
name = 'username'
|
||||
onChange = { this._onChange }
|
||||
placeholder = { t('dialog.userIdentifier') }
|
||||
shouldFitContainer = { true }
|
||||
type = 'text'
|
||||
value = { username } />
|
||||
<TextField
|
||||
className = 'input-control'
|
||||
compact = { false }
|
||||
label = { t('dialog.userPassword') }
|
||||
name = 'password'
|
||||
onChange = { this._onChange }
|
||||
shouldFitContainer = { true }
|
||||
type = 'password'
|
||||
value = { password } />
|
||||
{ this.renderMessage() }
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code LoginDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const {
|
||||
error: authenticateAndUpgradeRoleError,
|
||||
progress,
|
||||
thenableWithCancel
|
||||
} = state['features/authentication'];
|
||||
const { authRequired } = state['features/base/conference'];
|
||||
const { hosts: configHosts } = state['features/base/config'];
|
||||
const {
|
||||
connecting,
|
||||
error: connectionError
|
||||
} = state['features/base/connection'];
|
||||
|
||||
return {
|
||||
_conference: authRequired,
|
||||
_configHosts: configHosts,
|
||||
_connecting: connecting || thenableWithCancel,
|
||||
_error: connectionError || authenticateAndUpgradeRoleError,
|
||||
_progress: progress
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(reduxConnect(mapStateToProps)(LoginDialog));
|
||||
@@ -0,0 +1,129 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import { Dialog } from '../../../base/dialog';
|
||||
import { translate, translateToHTML } from '../../../base/i18n';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { openLoginDialog, cancelWaitForOwner } from '../../actions.web';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link WaitForOwnerDialog}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The name of the conference room (without the domain part).
|
||||
*/
|
||||
_room: string,
|
||||
|
||||
/**
|
||||
* Redux store dispatch method.
|
||||
*/
|
||||
dispatch: Dispatch<any>,
|
||||
|
||||
/**
|
||||
* Function to be invoked after click.
|
||||
*/
|
||||
onAuthNow: ?Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication message dialog for host confirmation.
|
||||
*
|
||||
* @returns {React$Element<any>}
|
||||
*/
|
||||
class WaitForOwnerDialog extends PureComponent<Props> {
|
||||
/**
|
||||
* Instantiates a new component.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this._onCancelWaitForOwner = this._onCancelWaitForOwner.bind(this);
|
||||
this._onIAmHost = this._onIAmHost.bind(this);
|
||||
}
|
||||
|
||||
_onCancelWaitForOwner: () => void;
|
||||
|
||||
/**
|
||||
* Called when the cancel button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCancelWaitForOwner() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(cancelWaitForOwner());
|
||||
}
|
||||
|
||||
_onIAmHost: () => void;
|
||||
|
||||
/**
|
||||
* Called when the OK button is clicked.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onIAmHost() {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(openLoginDialog());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
_room,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
okKey = { t('dialog.IamHost') }
|
||||
onCancel = { this._onCancelWaitForOwner }
|
||||
onSubmit = { this._onIAmHost }
|
||||
titleKey = { t('dialog.WaitingForHostTitle') }
|
||||
width = { 'small' }>
|
||||
<span>
|
||||
{
|
||||
translateToHTML(
|
||||
t, 'dialog.WaitForHostMsg', { room: _room })
|
||||
}
|
||||
</span>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps (parts of) the Redux state to the associated props for the
|
||||
* {@code WaitForOwnerDialog} component.
|
||||
*
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {Props}
|
||||
*/
|
||||
function mapStateToProps(state) {
|
||||
const { authRequired } = state['features/base/conference'];
|
||||
|
||||
return {
|
||||
_room: authRequired && authRequired.getName()
|
||||
};
|
||||
}
|
||||
|
||||
export default translate(connect(mapStateToProps)(WaitForOwnerDialog));
|
||||
4
react/features/authentication/components/web/index.js
Normal file
4
react/features/authentication/components/web/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
// @flow
|
||||
|
||||
export { default as WaitForOwnerDialog } from './WaitForOwnerDialog';
|
||||
export { default as LoginDialog } from './LoginDialog';
|
||||
25
react/features/authentication/functions.js
Normal file
25
react/features/authentication/functions.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// @flow
|
||||
|
||||
import JitsiMeetJS from '../../../react/features/base/lib-jitsi-meet';
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the token for authentication is available.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isTokenAuthEnabled = (config: Object) =>
|
||||
typeof config.tokenAuthUrl === 'string'
|
||||
&& config.tokenAuthUrl.length;
|
||||
|
||||
|
||||
/**
|
||||
* Token url.
|
||||
*
|
||||
* @param {Object} config - Configuration state object from store.
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getTokenAuthUrl = (config: Object) =>
|
||||
JitsiMeetJS.util.AuthUtil.getTokenAuthUrl.bind(null,
|
||||
config.tokenAuthUrl);
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './actions';
|
||||
export * from './actionTypes';
|
||||
export * from './components';
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
_openWaitForOwnerDialog,
|
||||
stopWaitForOwner,
|
||||
waitForOwner
|
||||
} from './actions';
|
||||
} from './actions.native';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
|
||||
/**
|
||||
134
react/features/authentication/middleware.web.js
Normal file
134
react/features/authentication/middleware.web.js
Normal file
@@ -0,0 +1,134 @@
|
||||
// @flow
|
||||
|
||||
import { maybeRedirectToWelcomePage } from '../app/actions';
|
||||
import {
|
||||
CONFERENCE_FAILED,
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
} from '../base/conference';
|
||||
import { CONNECTION_ESTABLISHED } from '../base/connection';
|
||||
import { hideDialog, isDialogOpen } from '../base/dialog';
|
||||
import {
|
||||
JitsiConferenceErrors
|
||||
} from '../base/lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
|
||||
import {
|
||||
CANCEL_LOGIN,
|
||||
STOP_WAIT_FOR_OWNER,
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
import {
|
||||
stopWaitForOwner,
|
||||
waitForOwner
|
||||
} from './actions.native';
|
||||
import {
|
||||
hideLoginDialog,
|
||||
openWaitForOwnerDialog
|
||||
} from './actions.web';
|
||||
import { LoginDialog, WaitForOwnerDialog } from './components';
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
if (!isDialogOpen(store, WaitForOwnerDialog)) {
|
||||
if (isWaitingForOwner(store)) {
|
||||
store.dispatch(openWaitForOwnerDialog());
|
||||
|
||||
return next(action);
|
||||
}
|
||||
|
||||
store.dispatch(hideLoginDialog());
|
||||
|
||||
store.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) {
|
||||
store.dispatch(waitForOwner());
|
||||
} else {
|
||||
store.dispatch(stopWaitForOwner());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_JOINED:
|
||||
if (isWaitingForOwner(store)) {
|
||||
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 WAIT_FOR_OWNER: {
|
||||
clearExistingWaitForOwnerTimeout(store);
|
||||
|
||||
const { handler, timeoutMs } = 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 }: { getState: Function }) {
|
||||
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 }: { getState: Function }) {
|
||||
return getState()['features/authentication'].waitForOwnerTimeoutID;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
/* @flow */
|
||||
// @flow
|
||||
|
||||
import { assign, ReducerRegistry } from '../base/redux';
|
||||
|
||||
@@ -10,6 +10,14 @@ import {
|
||||
WAIT_FOR_OWNER
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Listens for actions which change the state of the authentication feature.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the authentication feature.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register('features/authentication', (state = {}, action) => {
|
||||
switch (action.type) {
|
||||
case CANCEL_LOGIN:
|
||||
|
||||
@@ -17,6 +17,7 @@ export default [
|
||||
'audioLevelsInterval',
|
||||
'apiLogLevels',
|
||||
'avgRtpStatsN',
|
||||
'backgroundAlpha',
|
||||
|
||||
/**
|
||||
* The display name of the CallKit call representing the conference/meeting
|
||||
|
||||
@@ -96,6 +96,7 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
{ t(this.props.contentKey) }
|
||||
</Text>
|
||||
<TextInput
|
||||
autoFocus = { true }
|
||||
onChangeText = { this._onChangeText }
|
||||
style = { _dialogStyles.field }
|
||||
underlineColorAndroid = { FIELD_UNDERLINE }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* global APP */
|
||||
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiTrackErrors, browser } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE, setAudioMuted } from '../media';
|
||||
import { toState } from '../redux';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId
|
||||
@@ -475,3 +477,16 @@ export function setTrackMuted(track, muted) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether toggle camera should be enabled or not.
|
||||
*
|
||||
* @param {Function|Object} stateful - The redux store or {@code getState} function.
|
||||
* @returns {boolean} - Whether toggle camera should be enabled.
|
||||
*/
|
||||
export function isToggleCameraEnabled(stateful) {
|
||||
const state = toState(stateful);
|
||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return isMobileBrowser() && videoInput.length > 1;
|
||||
}
|
||||
|
||||
@@ -126,3 +126,62 @@ export function reportError(e: Object, msg: string = '') {
|
||||
console.error(msg, e);
|
||||
window.onerror && window.onerror(msg, null, null, null, e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds alpha to a color css string.
|
||||
*
|
||||
* @param {string} color - The color string either in rgb... Or #... Format.
|
||||
* @param {number} opacity -The opacity(alpha) to apply to the color. Can take a value between 0 and 1, including.
|
||||
* @returns {string} - The color with applied alpha.
|
||||
*/
|
||||
export function setColorAlpha(color: string, opacity: number) {
|
||||
if (!color) {
|
||||
return `rgba(0, 0, 0, ${opacity})`;
|
||||
}
|
||||
|
||||
let b, g, r;
|
||||
|
||||
try {
|
||||
if (color.startsWith('rgb')) {
|
||||
[ r, g, b ] = color.split('(')[1].split(')')[0].split(',').map(c => c.trim());
|
||||
} else if (color.startsWith('#')) {
|
||||
if (color.length === 4) {
|
||||
[ r, g, b ] = parseShorthandColor(color);
|
||||
} else {
|
||||
r = parseInt(color.substring(1, 3), 16);
|
||||
g = parseInt(color.substring(3, 5), 16);
|
||||
b = parseInt(color.substring(5, 7), 16);
|
||||
}
|
||||
} else {
|
||||
return color;
|
||||
}
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
} catch {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hexa rgb values for a shorthand css color.
|
||||
*
|
||||
* @param {string} color -
|
||||
* @returns {Array<number>} - Array containing parsed r, g, b values of the color.
|
||||
*/
|
||||
function parseShorthandColor(color) {
|
||||
let b, g, r;
|
||||
|
||||
r = color.substring(1, 2);
|
||||
r += r;
|
||||
r = parseInt(r, 16);
|
||||
|
||||
g = color.substring(2, 3);
|
||||
g += g;
|
||||
g = parseInt(g, 16);
|
||||
|
||||
b = color.substring(3, 4);
|
||||
b += b;
|
||||
b = parseInt(b, 16);
|
||||
|
||||
return [ r, g, b ];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { getConferenceNameForTitle } from '../../../base/conference';
|
||||
import { connect, disconnect } from '../../../base/connection';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { connect as reactReduxConnect } from '../../../base/redux';
|
||||
import { setColorAlpha } from '../../../base/util';
|
||||
import { Chat } from '../../../chat';
|
||||
import { Filmstrip } from '../../../filmstrip';
|
||||
import { CalleeInfoContainer } from '../../../invite';
|
||||
@@ -61,6 +62,11 @@ const LAYOUT_CLASSNAMES = {
|
||||
*/
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* The alpha(opacity) of the background
|
||||
*/
|
||||
_backgroundAlpha: number,
|
||||
|
||||
/**
|
||||
* Whether the local participant is recording the conference.
|
||||
*/
|
||||
@@ -98,6 +104,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
_onFullScreenChange: Function;
|
||||
_onShowToolbar: Function;
|
||||
_originalOnShowToolbar: Function;
|
||||
_setBackground: Function;
|
||||
|
||||
/**
|
||||
* Initializes a new Conference instance.
|
||||
@@ -121,6 +128,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onFullScreenChange = this._onFullScreenChange.bind(this);
|
||||
this._setBackground = this._setBackground.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,7 +194,8 @@ class Conference extends AbstractConference<Props, *> {
|
||||
<div
|
||||
className = { _layoutClassName }
|
||||
id = 'videoconference_page'
|
||||
onMouseMove = { this._onShowToolbar }>
|
||||
onMouseMove = { this._onShowToolbar }
|
||||
ref = { this._setBackground }>
|
||||
|
||||
<Notice />
|
||||
<div id = 'videospace'>
|
||||
@@ -208,6 +217,35 @@ class Conference extends AbstractConference<Props, *> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets custom background opacity based on config. It also applies the
|
||||
* opacity on parent element, as the parent element is not accessible directly,
|
||||
* only though it's child.
|
||||
*
|
||||
* @param {Object} element - The DOM element for which to apply opacity.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setBackground(element) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.props._backgroundAlpha !== undefined) {
|
||||
const elemColor = element.style.background;
|
||||
const alphaElemColor = setColorAlpha(elemColor, this.props._backgroundAlpha);
|
||||
|
||||
element.style.background = alphaElemColor;
|
||||
if (element.parentElement) {
|
||||
const parentColor = element.parentElement.style.background;
|
||||
const alphaParentColor = setColorAlpha(parentColor, this.props._backgroundAlpha);
|
||||
|
||||
element.parentElement.style.background = alphaParentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the Redux state when full screen mode has been enabled or
|
||||
* disabled.
|
||||
@@ -265,6 +303,7 @@ function _mapStateToProps(state) {
|
||||
return {
|
||||
...abstractMapStateToProps(state),
|
||||
_iAmRecorder: state['features/base/config'].iAmRecorder,
|
||||
_backgroundAlpha: state['features/base/config'].backgroundAlpha,
|
||||
_isLobbyScreenVisible: state['features/base/dialog']?.component === LobbyScreen,
|
||||
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
|
||||
_roomName: getConferenceNameForTitle(state),
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, { Component } from 'react';
|
||||
|
||||
import { Watermarks } from '../../base/react';
|
||||
import { connect } from '../../base/redux';
|
||||
import { setColorAlpha } from '../../base/util';
|
||||
import { Subject } from '../../conference';
|
||||
import { fetchCustomBrandingData } from '../../dynamic-branding';
|
||||
import { Captions } from '../../subtitles/';
|
||||
@@ -12,6 +13,11 @@ declare var interfaceConfig: Object;
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The alpha(opacity) of the background
|
||||
*/
|
||||
_backgroundAlpha: number,
|
||||
|
||||
/**
|
||||
* The user selected background color.
|
||||
*/
|
||||
@@ -121,6 +127,12 @@ class LargeVideo extends Component<Props> {
|
||||
|
||||
styles.backgroundColor = _customBackgroundColor || interfaceConfig.DEFAULT_BACKGROUND;
|
||||
|
||||
if (this.props._backgroundAlpha !== undefined) {
|
||||
const alphaColor = setColorAlpha(styles.backgroundColor, this.props._backgroundAlpha);
|
||||
|
||||
styles.backgroundColor = alphaColor;
|
||||
}
|
||||
|
||||
if (_customBackgroundImageUrl) {
|
||||
styles.backgroundImage = `url(${_customBackgroundImageUrl})`;
|
||||
styles.backgroundSize = 'cover';
|
||||
@@ -144,6 +156,7 @@ function _mapStateToProps(state) {
|
||||
const { isOpen: isChatOpen } = state['features/chat'];
|
||||
|
||||
return {
|
||||
_backgroundAlpha: state['features/base/config'].backgroundAlpha,
|
||||
_customBackgroundColor: backgroundColor,
|
||||
_customBackgroundImageUrl: backgroundImageUrl,
|
||||
_isChatOpen: isChatOpen,
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from '../../base/connection';
|
||||
import { JitsiConferenceEvents } from '../../base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../base/media';
|
||||
import { SET_AUDIO_MUTED } from '../../base/media/actionTypes';
|
||||
import { SET_AUDIO_MUTED, SET_VIDEO_MUTED } from '../../base/media/actionTypes';
|
||||
import { PARTICIPANT_JOINED, PARTICIPANT_LEFT, getParticipants, getParticipantById } from '../../base/participants';
|
||||
import { MiddlewareRegistry, StateListenerRegistry } from '../../base/redux';
|
||||
import { toggleScreensharing } from '../../base/tracks';
|
||||
@@ -209,6 +209,15 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
muted: action.muted
|
||||
});
|
||||
break;
|
||||
|
||||
case SET_VIDEO_MUTED:
|
||||
sendEvent(
|
||||
store,
|
||||
'VIDEO_MUTED_CHANGED',
|
||||
/* data */ {
|
||||
muted: action.muted
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -271,7 +280,11 @@ function _registerForNativeEvents(store) {
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_AUDIO_MUTED, ({ muted }) => {
|
||||
dispatch(muteLocal(muted === 'true', MEDIA_TYPE.AUDIO));
|
||||
dispatch(muteLocal(muted, MEDIA_TYPE.AUDIO));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SET_VIDEO_MUTED, ({ muted }) => {
|
||||
dispatch(muteLocal(muted, MEDIA_TYPE.VIDEO));
|
||||
});
|
||||
|
||||
eventEmitter.addListener(ExternalAPI.SEND_ENDPOINT_TEXT_MESSAGE, ({ to, message }) => {
|
||||
|
||||
@@ -108,6 +108,13 @@ function _visitNode(node, callback) {
|
||||
global.addEventListener = () => {};
|
||||
}
|
||||
|
||||
// Promise.allSettled is supported from RN 0.63 onwards, use a polyfill for that.
|
||||
// Invokes its shim method to shim Promise.allSettled if it is unavailable or noncompliant.
|
||||
//
|
||||
// Required by:
|
||||
// lib-jitsi-meet/JitsiConference.js
|
||||
require('promise.allsettled').shim();
|
||||
|
||||
// removeEventListener
|
||||
//
|
||||
// Required by:
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
RECORDING_ON_SOUND_FILE
|
||||
} from './sounds';
|
||||
|
||||
declare var APP: Object;
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
@@ -181,6 +182,10 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
if (soundID) {
|
||||
dispatch(playSound(soundID));
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(true, mode);
|
||||
}
|
||||
} else if (updatedSessionData.status === OFF
|
||||
&& (!oldSessionData || oldSessionData.status !== OFF)) {
|
||||
dispatch(showStoppedRecordingNotification(
|
||||
@@ -209,6 +214,10 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
dispatch(stopSound(soundOn));
|
||||
dispatch(playSound(soundOff));
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(false, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,11 +240,11 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
||||
* @returns {void}
|
||||
*/
|
||||
function _showRecordingErrorNotification(recorderSession, dispatch) {
|
||||
const isStreamMode
|
||||
= recorderSession.getMode()
|
||||
=== JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
const mode = recorderSession.getMode();
|
||||
const error = recorderSession.getError();
|
||||
const isStreamMode = mode === JitsiMeetJS.constants.recording.mode.STREAM;
|
||||
|
||||
switch (recorderSession.getError()) {
|
||||
switch (error) {
|
||||
case JitsiMeetJS.constants.recording.error.SERVICE_UNAVAILABLE:
|
||||
dispatch(showRecordingError({
|
||||
descriptionKey: 'recording.unavailable',
|
||||
@@ -270,4 +279,8 @@ function _showRecordingErrorNotification(recorderSession, dispatch) {
|
||||
}));
|
||||
break;
|
||||
}
|
||||
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.API.notifyRecordingStatusChanged(false, mode, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
SET_TIMEOUT,
|
||||
timerWorkerScript
|
||||
} from './TimerWorker';
|
||||
|
||||
const blurValue = '25px';
|
||||
|
||||
/**
|
||||
@@ -114,7 +113,13 @@ export default class JitsiStreamBackgroundEffect {
|
||||
|
||||
this._outputCanvasCtx.globalCompositeOperation = 'destination-over';
|
||||
if (this._options.virtualBackground.isVirtualBackground) {
|
||||
this._outputCanvasCtx.drawImage(this._virtualImage, 0, 0);
|
||||
this._outputCanvasCtx.drawImage(
|
||||
this._virtualImage,
|
||||
0,
|
||||
0,
|
||||
this._inputVideoElement.width,
|
||||
this._inputVideoElement.height
|
||||
);
|
||||
} else {
|
||||
this._outputCanvasCtx.filter = `blur(${blurValue})`;
|
||||
this._outputCanvasCtx.drawImage(this._inputVideoElement, 0, 0);
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// @flow
|
||||
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { IconCameraRefresh } from '../../../base/icons';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
import { isLocalCameraTrackMuted, toggleCamera } from '../../../base/tracks';
|
||||
import { isLocalCameraTrackMuted, isToggleCameraEnabled, toggleCamera } from '../../../base/tracks';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link ToggleCameraButton}.
|
||||
@@ -65,12 +64,11 @@ class ToggleCameraButton extends AbstractButton<Props, any> {
|
||||
function mapStateToProps(state): Object {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
const { videoInput } = state['features/base/devices'].availableDevices;
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(audioOnly),
|
||||
_videoMuted: isLocalCameraTrackMuted(tracks),
|
||||
visible: isMobileBrowser() && videoInput.length > 1
|
||||
visible: isToggleCameraEnabled(state)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
import { TILE_VIEW_ENABLED, getFeatureFlag } from '../../base/flags';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { IconTileView } from '../../base/icons';
|
||||
import { getParticipantCount } from '../../base/participants';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { setTileView } from '../actions';
|
||||
@@ -87,8 +86,7 @@ class TileViewButton<P: Props> extends AbstractButton<P, *> {
|
||||
*/
|
||||
function _mapStateToProps(state, ownProps) {
|
||||
const enabled = getFeatureFlag(state, TILE_VIEW_ENABLED, true);
|
||||
const lonelyMeeting = getParticipantCount(state) < 2;
|
||||
const { visible = enabled && !lonelyMeeting } = ownProps;
|
||||
const { visible = enabled } = ownProps;
|
||||
|
||||
return {
|
||||
_tileViewEnabled: shouldDisplayTileView(state),
|
||||
|
||||
@@ -107,13 +107,6 @@ export function getTileViewGridDimensions(state: Object) {
|
||||
export function shouldDisplayTileView(state: Object = {}) {
|
||||
const participantCount = getParticipantCount(state);
|
||||
|
||||
// In case of a lonely meeting, we don't allow tile view.
|
||||
// But it's a special case too, as we don't even render the button,
|
||||
// see TileViewButton component.
|
||||
if (participantCount < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tileViewEnabledFeatureFlag = getFeatureFlag(state, TILE_VIEW_ENABLED, true);
|
||||
const { disableTileView } = state['features/base/config'];
|
||||
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
// @flow
|
||||
/* eslint-disable react/jsx-no-bind, no-return-assign */
|
||||
import React, { useState } from 'react';
|
||||
import Spinner from '@atlaskit/spinner';
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import uuid from 'uuid';
|
||||
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconBlurBackground } from '../../base/icons';
|
||||
import { Icon, IconBlurBackground, IconCancelSelection } from '../../base/icons';
|
||||
import { connect } from '../../base/redux';
|
||||
import { Tooltip } from '../../base/tooltip';
|
||||
import { toggleBackgroundEffect, setVirtualBackground } from '../actions';
|
||||
import { resizeImage, toDataURL } from '../functions';
|
||||
import logger from '../logger';
|
||||
|
||||
// The limit of virtual background uploads is 21. When the number
|
||||
// of uploads is 22 we trigger the deleteStoredImage function to delete
|
||||
// the first/oldest uploaded background.
|
||||
const backgroundsLimit = 22;
|
||||
const images = [
|
||||
{
|
||||
tooltip: 'Image 1',
|
||||
name: 'background-1.jpg',
|
||||
id: 1,
|
||||
src: 'images/virtual-background/background-1.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'Image 2',
|
||||
name: 'background-2.jpg',
|
||||
id: 2,
|
||||
src: 'images/virtual-background/background-2.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'Image 3',
|
||||
name: 'background-3.jpg',
|
||||
id: 3,
|
||||
src: 'images/virtual-background/background-3.jpg'
|
||||
},
|
||||
{
|
||||
tooltip: 'Image 4',
|
||||
name: 'background-4.jpg',
|
||||
id: 4,
|
||||
src: 'images/virtual-background/background-4.jpg'
|
||||
}
|
||||
@@ -54,23 +55,81 @@ type Props = {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function VirtualBackground({ dispatch, t }: Props) {
|
||||
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
||||
const [ storedImages, setStoredImages ] = useState((localImages && JSON.parse(localImages)) || []);
|
||||
const [ loading, isloading ] = useState(false);
|
||||
|
||||
const deleteStoredImage = image => {
|
||||
setStoredImages(storedImages.filter(item => item !== image));
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates stored images on local storage.
|
||||
*/
|
||||
useEffect(() => {
|
||||
jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages));
|
||||
if (storedImages.length === backgroundsLimit) {
|
||||
deleteStoredImage(storedImages[0]);
|
||||
}
|
||||
}, [ storedImages ]);
|
||||
|
||||
const [ selected, setSelected ] = useState('');
|
||||
const enableBlur = () => {
|
||||
const enableBlur = async () => {
|
||||
isloading(true);
|
||||
setSelected('blur');
|
||||
dispatch(setVirtualBackground('', false));
|
||||
dispatch(toggleBackgroundEffect(true));
|
||||
await dispatch(setVirtualBackground('', false));
|
||||
await dispatch(toggleBackgroundEffect(true));
|
||||
isloading(false);
|
||||
};
|
||||
|
||||
const removeBackground = () => {
|
||||
const removeBackground = async () => {
|
||||
isloading(true);
|
||||
setSelected('none');
|
||||
dispatch(setVirtualBackground('', false));
|
||||
dispatch(toggleBackgroundEffect(false));
|
||||
await dispatch(setVirtualBackground('', false));
|
||||
await dispatch(toggleBackgroundEffect(false));
|
||||
isloading(false);
|
||||
};
|
||||
|
||||
const addImageBackground = image => {
|
||||
const setUploadedImageBackground = async image => {
|
||||
isloading(true);
|
||||
setSelected(image.id);
|
||||
dispatch(setVirtualBackground(image.src, true));
|
||||
dispatch(toggleBackgroundEffect(true));
|
||||
await dispatch(setVirtualBackground(image.src, true));
|
||||
await dispatch(toggleBackgroundEffect(true));
|
||||
isloading(false);
|
||||
};
|
||||
|
||||
const setImageBackground = async image => {
|
||||
isloading(true);
|
||||
setSelected(image.id);
|
||||
await dispatch(setVirtualBackground(await toDataURL(image.src), true));
|
||||
await dispatch(toggleBackgroundEffect(true));
|
||||
isloading(false);
|
||||
};
|
||||
|
||||
const uploadImage = async imageFile => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.readAsDataURL(imageFile[0]);
|
||||
reader.onload = async () => {
|
||||
const resizedImage = await resizeImage(reader.result);
|
||||
|
||||
isloading(true);
|
||||
setStoredImages([
|
||||
...storedImages,
|
||||
{
|
||||
id: uuid.v4(),
|
||||
src: resizedImage
|
||||
}
|
||||
]);
|
||||
|
||||
await dispatch(setVirtualBackground(resizedImage, true));
|
||||
await dispatch(toggleBackgroundEffect(true));
|
||||
isloading(false);
|
||||
};
|
||||
reader.onerror = () => {
|
||||
isloading(false);
|
||||
logger.error('Failed to upload virtual image!');
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -79,38 +138,79 @@ function VirtualBackground({ dispatch, t }: Props) {
|
||||
submitDisabled = { false }
|
||||
titleKey = { 'virtualBackground.title' }
|
||||
width = 'small'>
|
||||
<div className = 'virtual-background-dialog'>
|
||||
<Tooltip
|
||||
content = { t('virtualBackground.removeBackground') }
|
||||
position = { 'top' }>
|
||||
<div
|
||||
className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
|
||||
onClick = { () => removeBackground() }>
|
||||
None
|
||||
{loading ? (
|
||||
<div>
|
||||
<span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
|
||||
<Spinner
|
||||
isCompleting = { false }
|
||||
size = 'medium' />
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className = 'virtual-background-dialog'>
|
||||
<Tooltip
|
||||
content = { t('virtualBackground.removeBackground') }
|
||||
position = { 'top' }>
|
||||
<div
|
||||
className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
|
||||
onClick = { removeBackground }>
|
||||
{t('virtualBackground.none')}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content = { t('virtualBackground.enableBlur') }
|
||||
position = { 'top' }>
|
||||
<Icon
|
||||
className = { selected === 'blur' ? 'blur-selected' : '' }
|
||||
onClick = { () => enableBlur() }
|
||||
size = { 50 }
|
||||
src = { IconBlurBackground } />
|
||||
</Tooltip>
|
||||
{images.map((image, index) => (
|
||||
<img
|
||||
className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
||||
key = { index }
|
||||
onClick = { () => setImageBackground(image) }
|
||||
onError = { event => event.target.style.display = 'none' }
|
||||
src = { image.src } />
|
||||
))}
|
||||
<Tooltip
|
||||
content = { t('virtualBackground.uploadImage') }
|
||||
position = { 'top' }>
|
||||
<label
|
||||
className = 'custom-file-upload'
|
||||
htmlFor = 'file-upload'>
|
||||
+
|
||||
</label>
|
||||
<input
|
||||
accept = 'image/*'
|
||||
className = 'file-upload-btn'
|
||||
id = 'file-upload'
|
||||
onChange = { e => uploadImage(e.target.files) }
|
||||
type = 'file' />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content = { t('virtualBackground.enableBlur') }
|
||||
position = { 'top' }>
|
||||
<Icon
|
||||
className = { selected === 'blur' ? 'blur-selected' : '' }
|
||||
onClick = { () => enableBlur() }
|
||||
size = { 50 }
|
||||
src = { IconBlurBackground } />
|
||||
</Tooltip>
|
||||
{images.map((image, index) => (
|
||||
<Tooltip
|
||||
content = { image.tooltip }
|
||||
key = { index }
|
||||
position = { 'top' }>
|
||||
<img
|
||||
className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
||||
onClick = { () => addImageBackground(image) }
|
||||
onError = { event => event.target.style.display = 'none' }
|
||||
src = { image.src } />
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className = 'virtual-background-dialog'>
|
||||
{storedImages.map((image, index) => (
|
||||
<div
|
||||
className = { 'thumbnail-container' }
|
||||
key = { index }>
|
||||
<img
|
||||
className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
||||
onClick = { () => setUploadedImageBackground(image) }
|
||||
onError = { event => event.target.style.display = 'none' }
|
||||
src = { image.src } />
|
||||
<Icon
|
||||
className = { 'delete-image-icon' }
|
||||
onClick = { () => deleteStoredImage(image) }
|
||||
size = { 15 }
|
||||
src = { IconCancelSelection } />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// @flow
|
||||
|
||||
|
||||
let filterSupport;
|
||||
|
||||
/**
|
||||
@@ -20,3 +18,62 @@ export function checkBlurSupport() {
|
||||
|
||||
return filterSupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert blob to base64.
|
||||
*
|
||||
* @param {Blob} blob - The link to add info with.
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const blobToData = (blob: Blob): Promise<string> => new Promise(resolve => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onloadend = () => resolve(reader.result.toString());
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
|
||||
/**
|
||||
* Convert blob to base64.
|
||||
*
|
||||
* @param {string} url - The image url.
|
||||
* @returns {Object} - Returns the converted blob to base64.
|
||||
*/
|
||||
export const toDataURL = async (url: string) => {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const resData = await blobToData(blob);
|
||||
|
||||
return resData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize image and adjust original aspect ratio.
|
||||
*
|
||||
* @param {Object} base64image - Base64 image extraction.
|
||||
* @param {number} width - Value for resizing the image width.
|
||||
* @param {number} height - Value for resizing the image height.
|
||||
* @returns {Object} Returns the canvas output.
|
||||
*
|
||||
*/
|
||||
export async function resizeImage(base64image: any, width: number = 1920, height: number = 1080) {
|
||||
const img = document.createElement('img');
|
||||
|
||||
img.src = base64image;
|
||||
/* eslint-disable no-empty-function */
|
||||
img.onload = await function() {};
|
||||
|
||||
// Create an off-screen canvas.
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Set its dimension to target size.
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
// Draw source image into the off-screen canvas.
|
||||
// TODO: keep aspect ratio and implement object-fit: cover.
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
|
||||
// Encode image to data-uri with base64 version of compressed image.
|
||||
return canvas.toDataURL('image/jpeg', 0.5);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
local filters = require 'util.filters';
|
||||
local jid = require "util.jid";
|
||||
local jid_bare = require "util.jid".bare;
|
||||
local jid_host = require "util.jid".host;
|
||||
local um_is_admin = require "core.usermanager".is_admin;
|
||||
local util = module:require "util";
|
||||
local is_healthcheck_room = util.is_healthcheck_room;
|
||||
@@ -116,12 +117,24 @@ function filter_stanza(stanza)
|
||||
return stanza;
|
||||
end
|
||||
|
||||
-- we want to filter presences only on this host for allowners and skip anything like lobby etc.
|
||||
local host_from = jid_host(stanza.attr.from);
|
||||
if host_from ~= module.host then
|
||||
return stanza;
|
||||
end
|
||||
|
||||
local bare_to = jid_bare(stanza.attr.to);
|
||||
if stanza:get_error() and joining_moderator_participants[bare_to] then
|
||||
-- pre-join succeeded but joined did not so we need to clear cache
|
||||
joining_moderator_participants[bare_to] = nil;
|
||||
return stanza;
|
||||
end
|
||||
|
||||
local muc_x = stanza:get_child('x', MUC_NS..'#user');
|
||||
if not muc_x then
|
||||
return stanza;
|
||||
end
|
||||
|
||||
local bare_to = jid_bare(stanza.attr.to);
|
||||
if joining_moderator_participants[bare_to] and presence_check_status(muc_x, '110') then
|
||||
-- skip the local presence for participant
|
||||
return nil;
|
||||
|
||||
Reference in New Issue
Block a user